Я пытаюсь понять, как работает RVO в связке с shared_ptr в данном конкретном случае.
Скажем, у меня есть этот пример кода:
class A {
public:
void action() {}
};
class Container {
public:
shared_ptr<A> getA() { return m_a; }
private:
shared_ptr<A> m_a;
};
Container cont{};
cont.getA()->action();
Если я не ошибаюсь, в этой ситуации shared_ptr, возвращаемый getA(), не следует копировать/создавать, потому что он оптимизирован компилятором.
Итак, в последней строке функция shared_ptr, для которой я вызываю функцию action(), должна быть непосредственно той, которая содержится в m_a внутри объекта Container?
В этом случае, если указатель не копируется, внутренний счетчик ссылок не увеличивается/не уменьшается?
И поскольку я использую его как r-значение, оно оптимизировано, и я могу напрямую использовать его для доступа к указанному объекту без каких-либо затрат, как необработанный указатель?
В случае, если это не так, есть ли способ избежать затрат на увеличение/уменьшение? На самом деле я не сохраняю shared_ptr, но использую его только для операции над содержащимся объектом.
Или может быть проблема со временем жизни содержащегося объекта?
shared_ptr - разделить владение. Если вы не хотите делиться правом собственности, вы можете вернуть необработанный указатель или ссылку.
@ 463035818_is_not_a_number, а если я хочу оба? может быть, в какой-то ситуации мне нужно разделить право собственности, а в какой-то другой мне нужно только выполнить операцию над указанным объектом... Мне нужно создать для разных геттеров?
Я бы не слишком беспокоился о копировании общего ptr. Если action действительно что-то делает, то увеличение и уменьшение счетчика ссылок и создание копии, вероятно, не будут иметь значения для производительности. Может быть, это и не так, но вы можете узнать это, только написав код и измерив
геттеры - не самое приятное, что вы можете сделать с дизайном класса в целом. Подумайте над тем, чтобы написать Container::action. Или посмотрите, что делают стандартные контейнеры: они предоставляют вам прямой доступ к своим элементам, они не пытаются скрыть их от вас. Что будет хорошо для вашего случая, зависит от деталей и того, что такое Container, A и action.
Вернуть ссылку на существующий shared_ptr из shared_ptr<A>A& getA(). Если вы храните его где-то, вы делаете копию. В противном случае вы можете просто использовать ссылку в выражении.
@ 463035818_is_not_a_number еще раз спасибо! Я хотел бы обсудить свой случай, потому что у него есть некоторые ограничения на операции, которые я могу выполнять, которые не являются свободными от блокировки и делают создание хорошего дизайна очень трудным, но я бы ушел от темы здесь...
Просто не делайте ((new Container()).getA()).action() или Container переживёт скобки?
@Себастьян, спасибо! Это была одна из вещей, которую я рассмотрел в первую очередь, но, как я видел в некоторых других ответах на эту тему, в этом случае я потеряю инкапсуляцию частного члена, и это считается плохим дизайном, потому что кто-то может использовать его неправильно и сохранить ссылка на указанный объект без увеличения счетчика ссылок общего указателя
В большинстве (не во всех) случаях использование shared_ptr является плохим дизайном (ИМХО) - нет четкого владения, нет четкого времени жизни, возможных циклов, накладных расходов во время выполнения, но это всегда зависит от требований. Есть некоторые языки, для которых общие указатели используются по умолчанию.





Shared_ptr, возвращаемый getA(), не следует копировать/создавать, потому что он оптимизирован компилятором.
Да, он будет скопирован. m_a не является локальной переменной, поэтому NRVO (названная оптимизация возвращаемого значения) не может быть выполнена, и возврат ее по значению действительно скопирует конструкцию возвращаемого shared_ptr<A>.
спасибо, я упустил тот факт, что он мог работать только с локальными переменными, и надеялся, что компилятор в этом случае, когда я не сохраняю значение, указал бы непосредственно на член класса как на оптимизацию.
@MixKira Добро пожаловать! Что делает (N)RVO, так это создает объект непосредственно в месте вызова, поэтому функция должна фактически создать объект. Также обратите внимание, что параметры функции не учитываются и не могут использоваться в NRVO.
В двух словах...
#include <iostream>
struct foo {
foo() = default;
foo(const foo&) { std::cout << "copy\n";}
};
foo bar() {
foo f;
return f;
}
int main() {
foo g;
g = bar();
}
Не исключая копию: вы вызываете функцию, внутри функции есть f, функция возвращается, f копируется в возвращаемое значение, f уничтожается, теперь у вас есть g, копия f.
Идея исключения копии такова: короткий момент, когда копия делается в стороне, никогда не бывает более 1 объекта. Единственным эффектом отказа от копирования является отсутствие копирования, т. е. побочные эффекты могут быть пропущены, а приведенный выше код ничего не печатает.
В вашем случае это не применимо. m_a является участником. Это не локальная переменная функции. Необходимо сделать копию, поскольку объект, возвращаемый функцией, отличается от члена.
Live Demo с отключенным копированием
Да, временное значение, возвращаемое функцией, является копией. Счетчик ссылок общего указателя уменьшается в конце полного выражения.
спасибо, теперь это более ясно... Из того, что я понял о RVO, это был псевдоним сортировки, и поэтому я предположил, что NRVO также может применяться к возвращенным членам класса, поскольку это то, что компилятор знает, где он находится в памяти (связанный к объекту) и может указывать на тот же объект в памяти.
@MixKira, это было бы shared_ptr<A>& getA() { return m_a; }. Удаление копии не эквивалентно возврату ссылки
Вы всегда можете просто заменить
shared_ptrнастраиваемым классом, который регистрирует свои экземпляры и операции копирования/перемещения, и тогда вы сможете точно увидеть, что на самом деле делает компилятор.