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

Можем ли мы вернуть объекты, имеющие удаленный/закрытый экземпляр/механизм перемещения по значению из функции?

В С++ 03 невозможно вернуть объект класса, имеющий частный не определенный конструктор копирования, по значению:

struct A { A(int x) { ... } private: A(A const&); };

A f() {
  return A(10); // error!
  return 10;    // error too!
}

Мне было интересно, было ли это ограничение отменено на С++ 11, позволяя писать функции, имеющие тип возвращаемого типа типа для классов без конструкторов, используемых для копирования или перемещения? Я помню, что было бы полезно разрешить вызывающим функциям использовать вновь возвращенный объект, но они не могут скопировать значение и сохранить его где-нибудь.

4b9b3361

Ответ 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, хотя он ограничивает вас одной копией на экземпляр.