我们可以通过函数中的值返回具有已删除/私有复制/移动构造函数的对象吗

Can we return objects having a deleted/private copy/move constructor by value from a function?

本文关键字:复制 移动 构造函数 对象 可以通过 删除 函数 返回 我们      更新时间:2023-10-16

在C++03中,不可能按值返回具有私有未定义复制构造函数的类的对象:

struct A { A(int x) { ... } private: A(A const&); };
A f() {
  return A(10); // error!
  return 10;    // error too!
}

我想知道,C++11中是否取消了这一限制,从而可以为没有用于复制或移动的构造函数的类编写具有类类型返回类型的函数?我记得允许函数的调用方使用新返回的对象可能很有用,但他们不能复制值并将其存储在某个地方。

以下是它如何工作

A f() {
  return { 10 };
}

即使A没有工作的复制或移动构造函数,也没有其他可以复制或移动A的构造函数,这也能工作!

为了利用C++11的这一特性,构造函数(在本例中采用int(必须是非显式的。

限制尚未解除。根据访问说明符,§12.8/32中有一条注释解释道:

无论是否会发生复制省略,都必须执行两阶段过载解析。如果不执行省略,它将确定要调用的构造函数,并且即使调用被省略,所选的构造函数也必须是可访问的。

截至删除的复制/移动构造函数§8.4.3/2规定

隐式或显式引用已删除函数而不是声明该函数的程序是格式错误的。[注意:这包括隐式或显式调用函数,并形成指向函数成员的指针或指针。它甚至适用于未潜在求值的表达式中的引用。如果函数重载,则只有在通过重载解析选择函数时才会引用它。--结束注]

不确定这个特定的情况,但我对这句话的理解是,如果在§12.8/32中的重载解决方案之后,选择了已删除的复制/移动构造函数,即使该操作被省略,也可能构成对该函数的引用,并且程序将是格式错误的。

上述代码在C++11中仍然格式不正确。但是您可以在A中添加一个公共移动构造函数,这样它就合法了:

struct A
{
    A(int x) {}
    A(A&&);
private:
    A(A const&);
};
A f() {
  return A(10); // Ok!
}

我想知道,这个限制在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的指针或其他什么(,但内存中最多会有一个存储实际指针的位置。

删除复制和移动构造函数将创建一个不动的对象。创建它的地方就是它的价值所在,永远。移动可以让你拥有独特的所有权,但不会静止不动。

如果你真的想的话,你可能会破解一个代理来完成这项任务,并有一个转换构造函数来复制存储在代理中的值。

大致如下:

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_ptrs执行同样的操作。而且它显然不会阻止存储生成的Object,尽管它确实将您的每个实例限制为一个副本。