Подтвердить что ты не робот

Общий указатель на неизменяемый тип имеет семантику значения

Шон Родитель дал слово Going Native 2013 под названием Наследование - это базовый класс зла. Через 20 минут, 50 секунд, он делает утверждение, что общий указатель на неизменяемый (const) тип (std::shared_pointer<const T>) имеет семантику значений. Что именно это значит? Почему это отличается от общего указателя на изменяемый (неконстантный) тип (std::shared_pointer<T>)?

4b9b3361

Ответ 1

К сожалению, как и все разговоры на Going Native 2013, он был ограничен очень ограниченным графиком. К счастью для нас, однако, Шон Родитель в прошлом году дал более обстоятельный разговор в С++. Теперь называется Ценностная семантика и концептуальный полиморфизм. Он охватывает тот же материал и, вероятно, ответит на ваши вопросы. Я все равно сделаю все возможное...

Введение

Существует два типа семантики, которые могут иметь тип:

  • Семантика значения.
  • Ссылочная семантика. (Иногда называется семантикой указателя.)

На многих страницах можно рассказывать о том, как они отличаются друг от друга, и когда один предпочтительнее другого. Позвольте просто сказать, что код с использованием типов значений может быть более легко обоснован.

То есть ничего непредсказуемого не происходит в какой-либо точке экземпляра типа значения - то, что не может быть гарантировано с помощью ссылочного типа, поскольку значение, на которое ссылаются, разделяется между другими частями вашего кода, которые содержат ссылку к нему.

Другими словами: ссылочные типы менее предсказуемы, потому что они могут быть изменены удаленным фрагментом кода. Например, функция, которую вы вызываете, может изменить значение, указанное из-под вас. Или, что еще хуже, если задействована потоковая передача, ссылочный тип может быть изменен в любой момент другим потоком, который будет работать с указанным значением. По этой причине Шон Родитель делает выражение о том, что a shared_ptr не хуже глобальной переменной, когда речь заходит о возможности использования кода, который использует его.

Со всем сказанным мы должны быть готовы ответить на поставленный вопрос.


Вопрос и ответ

Для типа значения T, почему a shared_ptr<const T> действует как тип значения, даже если это тип указателя?

Поскольку мы не можем вносить изменения в const T, на которые указываются, все, что было сказано о том, что типы указателя/ссылки менее предсказуемы, больше не применяется. Нам больше не нужно беспокоиться о том, что T неожиданно изменяется, потому что это тип значения const.

Если бы мы захотели внести изменения в T, нам пришлось бы сделать его копию, оставив другие, у которых shared_ptr<const T> не повлияли бы наши действия. Более того, копия может даже быть скрыта внутри типа значения, используя механизм, называемый Copy-on-write, который, кажется, является тем, что Шон Родитель в конечном счете сделал.


Думаю, я ответил на вопрос, как Шон Родитель (и сделал это в связанной презентации С++ Now), но давайте немного покончим с добавлением.....

Большое приложение:

(Спасибо @BretKuhns за то, что он привел это и предоставил пример в комментариях.)

В этом целом понятии есть одна ворчащая вещь. Утверждение, что shared_ptr<const T> ведет себя как тип значения, не обязательно правильно, если мы не знаем, что все живые указатели/ссылки на этот экземпляр T равны const. Это связано с тем, что модификатор const является односторонней улицей - размещение shared_ptr<const T> может помешать нам модифицировать экземпляр T, но не мешает кому-либо другому изменять T с помощью указателя/ссылки на не- const.

Зная это, я устал бы делать широкое утверждение, что shared_ptr<const T> так же хорош, как тип значения, если я не знаю, что все живые указатели на него const. Но, зная, что такая вещь требует глобального знания кода обо всех использованиях shared_ptr<const T> - то, что не будет проблемой для типа значения. По этой причине может иметь смысл сказать что-то вроде: A shared_ptr<const T> может использоваться для поддержки семантики значений.


С другой стороны, я был на самом деле в Going Native 2013 - возможно, вы можете увидеть заднюю часть моей головы спереди слева.

Ответ 2

Я приведу 3 примера. Во всех трех случаях я создаю переменную a с содержимым "original value". Затем я создаю другую переменную b, произнося auto b = a;, и после этого оператора я назначаю a содержимое "new value".

Если a и b имеют семантику значений, я ожидаю, что b content будет "original content". И на самом деле это происходит с string и shared_ptr<const string>. Концептуальный смысл auto b = a; одинаковый с этими типами. Не так много с shared_ptr<string>, b будет иметь содержимое "new value".

Код (онлайн-демонстрация):

#include <iostream>
#include <memory>
#include <string>

using namespace std;

void string_example() {

    auto a = string("original value");

    auto b = a; // true copy by copying the value

    a = string("new value");

    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}

void shared_ptr_example() {

    auto a = make_shared<string>("original value");

    auto b = a; // not a copy, just and alias

    *a = string("new value"); // and this gonna hurt b

    cout << "a = " << *a << endl;
    cout << "b = " << *b << endl;
    cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}

void shared_ptr_to_const_example() {

    auto a = make_shared<const string>("original value");

    auto b = a;

    //*a = string("new value"); // <-- now won't compile
    a = make_shared<const string>("new value");

    cout << "a = " << *a << endl;
    cout << "b = " << *b << endl;
    cout << boolalpha << "&a == &b ? " << (&a==&b) << endl;
}

int main() {

    cout << "--------------" << endl;
    cout << "string example" << endl;
    string_example();

    cout << "------------------" << endl;
    cout << "shared_ptr example" << endl;
    shared_ptr_example();

    cout << "---------------------------" << endl;
    cout << "shared_ptr to const example" << endl;
    shared_ptr_to_const_example();
}

Вывод:

--------------
string example
a = new value
b = original value
&a == &b ? false
------------------
shared_ptr example
a = new value
b = new value
&a == &b ? false
---------------------------
shared_ptr to const example
a = new value
b = original value
&a == &b ? false

Сказав это, я бы хотел, чтобы у него было немного больше времени. Есть несколько вещей, о которых мне все еще интересно после этой презентации. Я довольно убежден, что это было только нехватка времени, он кажется отличным докладчиком.

Ответ 3

То, что он имеет в виду, это то, что они могут использоваться для эмуляции семантики значений.

Основная определяющая черта семантики значения состоит в том, что два объекта с одним и тем же содержимым одинаковы. Целые значения - это типы значений: a 5 - это то же самое, что и любое другое. 5. Сравните это с эталонной механикой, где объекты имеют идентификатор. Список a, содержащий [1, 2], не совпадает с списком b, содержащим [1, 2], поскольку добавление 3 к a не имеет такого же эффекта, как добавление 3 к b. Тождество a отличается от тождества b.

Это имеет тенденцию быть интуитивно понятным... это просто звучит странно, когда вставлять слова. Никто не делает это 3 дня на С++, не получая интуитивно понятного значения типов значений по сравнению с ссылочными типами.

Если у вас есть изменяемый тип значения и вы хотите его скопировать, вы должны фактически скопировать содержимое объекта. Это дорого.

Тройка Шон ссылается на то, что если объект неизменен, то вам не нужно копировать весь объект, вы можете просто ссылаться на старый. Это намного быстрее.

Ответ 4

Он, по-видимому, полагает, что существование a shared_ptr<const T> означает, что все дескрипторы объекта также shared_ptr<const T> (то есть, только для чтения).

Это, конечно, не более верно, чем наличие raw const T* является доказательством того, что объект const.

Демо: http://ideone.com/UuHsEj

Вероятно, вы ошибаетесь "неизменность" для значения const T - в вопросе, который вы сказали, что они одинаковы, но это не так.