Можем ли мы вернуть объекты, имеющие удаленный/закрытый экземпляр/механизм перемещения по значению из функции?
В С++ 03 невозможно вернуть объект класса, имеющий частный не определенный конструктор копирования, по значению:
struct A { A(int x) { ... } private: A(A const&); };
A f() {
return A(10); // error!
return 10; // error too!
}
Мне было интересно, было ли это ограничение отменено на С++ 11, позволяя писать функции, имеющие тип возвращаемого типа типа для классов без конструкторов, используемых для копирования или перемещения? Я помню, что было бы полезно разрешить вызывающим функциям использовать вновь возвращенный объект, но они не могут скопировать значение и сохранить его где-нибудь.
Ответы
Ответ 1
Вот как это работает
A f() {
return { 10 };
}
Это работает, хотя A
не имеет рабочей копии или перемещает конструктор и никакого другого конструктора, который мог бы скопировать или переместить A
!
Чтобы использовать эту функцию С++ 11, конструктор (принимая в этом случае int
) должен быть неявным, хотя.
Ответ 2
Ограничение не отменено. В соответствии с спецификатором доступа в §12.8/32 есть примечание, которое объясняет:
двухступенчатое разрешение перегрузки должно выполняться независимо от того, произойдет ли копирование. Он определяет вызывающий конструктор, если elision не выполняется, и выбранный конструктор должен быть доступен, даже если вызов завершен.
Как и в случае с удаленными конструкторами copy/move в §8.4.3/2,
Программа, которая ссылается на удаленную функцию неявно или явно, кроме как объявить ее, плохо сформирована. [Примечание. Это включает вызов функции неявно или явно и формирование указателя или указателя на элемент функции. Он применяется даже для ссылок в выражениях, которые потенциально не оцениваются. Если функция перегружена, она ссылается только в том случае, если функция выбрана с помощью разрешения перегрузки. - конечная нота]
Не уверен в этом конкретном случае, но мое понимание цитаты состоит в том, что если после разрешения перегрузки в §12.8/32 выбран удаленный экземпляр/механизм перемещения, даже если операция будет устранена, это может быть ссылкой к функции, и программа будет плохо сформирована.
Ответ 3
Вышеприведенный код по-прежнему плохо сформирован в С++ 11. Но вы могли бы добавить открытый конструктор перемещения в A
, а затем он был бы законным:
struct A
{
A(int x) {}
A(A&&);
private:
A(A const&);
};
A f() {
return A(10); // Ok!
}
Ответ 4
Мне было интересно, было ли это ограничение снято в С++ 11?
Как это могло быть? Возвращая что-то по значению, вы по определению копируете (или перемещаете) его. И хотя С++ может разрешить копирование/перемещение в определенных обстоятельствах, он все еще копирует (или перемещает) спецификацию.
Я помню, что было бы полезно разрешить вызывающим функциям использовать возвращаемый объект, но они не могут скопировать значение и сохранить его где-нибудь.
Да. Вы избавляетесь от конструктора/назначения копии, но допускаете перемещение значения. std::unique_ptr
делает это.
Вы можете вернуть значение unique_ptr
по значению. Но при этом вы возвращаете "prvalue": временное, которое уничтожается. Поэтому, если у вас есть функция g
как таковая:
std::unique_ptr<SomeType> g() {...}
Вы можете сделать это:
std::unique_ptr<SomeType> value = g();
Но не это:
std::unique_ptr<SomeType> value1 = g();
std::unique_ptr<SomeType> value2 = g();
value1 = value 2;
Но это возможно:
std::unique_ptr<SomeType> value = g();
value = g();
Вторая строка вызывает оператор присваивания перемещения на value
. Он удалит старый указатель и переместит в него новый указатель, оставив старое значение пустым.
Таким образом, вы можете гарантировать, что содержимое любого unique_ptr
хранится только в одном месте. Вы не можете помешать им ссылаться на него в нескольких местах (через указатели на unique_ptr
или что-то еще), но будет находиться не более одного места в памяти, где хранится фактический указатель.
Удаление конструкторов копирования и перемещения создает неподвижный объект. Там, где он создан, он всегда остается навсегда. Движение позволяет вам иметь уникальную собственность, но не быть неподвижным.
Ответ 5
Возможно, вы могли бы взломать прокси-сервер, чтобы сделать трюк, если хотите, и иметь конструктор преобразования, который копирует значение, хранящееся в прокси-сервере.
Что-то по строкам:
template<typename T>
struct ReturnProxy {
//This could be made private, provided appropriate frienship is granted
ReturnProxy(T* p_) : p(p_) { }
ReturnProxy(ReturnProxy&&) = default;
private:
//don't want these Proxies sticking around...
ReturnProxy(const ReturnProxy&) = delete;
void operator =(const ReturnProxy&) = delete;
void operator =(ReturnProxy&&) = delete;
struct SUPER_FRIENDS { typedef T GO; };
friend struct SUPER_FRIENDS::GO;
unique_ptr<T> p;
};
struct Object {
Object() : data(0) { }
//Pseudo-copy constructor
Object(ReturnProxy<Object>&& proxy)
: data(proxy.p ? proxy.p->data : throw "Don't get sneaky with me \\glare")
{
//steals `proxy.p` so that there isn't a second copy of this object floating around
//shouldn't be necessary, but some men just want to watch the world burn.
unique_ptr<Object> thief(std::move(proxy.p));
}
private:
int data;
Object(const Object&) = delete;
void operator =(const Object&) = delete;
};
ReturnProxy<Object> func() {
return ReturnProxy(new Object);
}
int main() {
Object o(func());
}
Возможно, вы могли бы сделать то же самое в 03, но используя auto_ptr
s. И это, очевидно, не мешает хранению результирующего Object
, хотя он ограничивает вас одной копией на экземпляр.