Можем ли мы вернуть объекты, имеющие удаленный / закрытый конструктор копирования / перемещения, по значению из функции? - PullRequest
16 голосов
/ 29 октября 2011

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

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

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

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

Ответы [ 5 ]

14 голосов
/ 29 октября 2011

Вот как это может работать

A f() {
  return { 10 };
}

Это работает, даже если A не имеет рабочего конструктора для копирования или перемещения и другого конструктора, который мог бы копировать или перемещать A!

Чтобы использовать эту функцию C ++ 11, конструктор (в данном случае int) должен быть неявным.

5 голосов
/ 29 октября 2011

Ограничение не было снято.Что касается спецификатора доступа, в §12.8 / 32 есть примечание, в котором объясняется:

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

Начиная с удаленных конструкторов копирования / перемещения §8.4.3 / 2заявляет, что

Программа, которая явно или неявно ссылается на удаленную функцию, кроме как для ее объявления, является неправильно сформированной.[Примечание: это включает в себя вызов функции неявно или явно и формирование указателя или указателя на член функции.Это относится даже к ссылкам в выражениях, которые потенциально не оцениваются.Если функция перегружена, на нее ссылаются, только если функция выбрана разрешением перегрузки.- примечание конца]

Не уверен насчет этого конкретного случая, но мое понимание цитаты таково, что если после разрешения перегрузки в §12.8 / 32 выбран удаленный конструктор копирования / перемещения, даже еслиоперация исключается, что может составлять ссылку на функцию, и программа будет плохо сформирована.

4 голосов
/ 29 октября 2011

Приведенный выше код все еще плохо сформирован в C ++ 11. Но вы можете добавить открытый конструктор перемещения к A, и тогда это будет разрешено:

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

A f() {
  return A(10); // Ok!
}
1 голос
/ 29 октября 2011

Мне было интересно, было ли снято это ограничение в C ++ 11?

Как это могло быть? Возвращая что-то по значению, вы по определению копируете (или перемещаете) это. И хотя C ++ может разрешить исключение этого копирования / перемещения при определенных обстоятельствах, оно все равно копируется (или перемещается) в соответствии со спецификацией.

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

Да. Вы избавляетесь от конструктора / назначения копирования, но допускаете перемещение значения . 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 или что-то еще), но в памяти будет не более одного места, где хранится фактический указатель.

Удаление конструкторов копирования и перемещения создает объект неподвижный . Где он создан, там и остаются его значения, forever . Движение позволяет вам иметь уникальную собственность, но не быть неподвижным.

0 голосов
/ 29 октября 2011

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

Что-то вроде:

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

...