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

发布于 2024-12-12 17:07:54 字数 304 浏览 0 评论 0原文

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

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

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

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

In C++03 it is impossible to return an object of a class having a private non-defined copy constructor by value:

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

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

I was wondering, was this restriction lifted in C++11, making it possible to write functions having a class type return type for classes without constructors used for copy or move? I remember it could be useful to allow callers of a function use the newly returned object, but that they are not able to copy the value and store it somewhere.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

2024-12-19 17:07:54

这是它的工作原理

A f() {
  return { 10 };
}

即使 A 没有工作副本或移动构造函数,也没有其他可以复制或移动 A 的构造函数,它仍然有效!

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

Here is how it can work

A f() {
  return { 10 };
}

This works even though A has no working copy or move constructor and no other constructor that could copy or move an A!

To make use of this feature of C++11, the constructor (taking int in this case) has to be non-explicit though.

风月客 2024-12-19 17:07:54

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

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

从删除的复制/移动构造函数开始,§8.4.3/2 指出

除了声明之外,隐式或显式引用已删除函数的程序是格式错误的。 [ 注意:这包括隐式或显式调用函数以及形成函数的指针或指向成员的指针。它甚至适用于未潜在评估的表达式中的引用。如果函数被重载,则仅当该函数被重载决策选择时才会引用该函数。 ——尾注]

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

The restriction has not been lifted. As per the access specifier, there is a note in §12.8/32 that explains:

two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided.

As of the deleted copy/move constructors §8.4.3/2 states that

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed. [ Note: This includes calling the function implicitly or explicitly and forming a pointer or pointer-to-member to the function. It applies even for references in expressions that are not potentially-evaluated. If a function is overloaded, it is referenced only if the function is selected by overload resolution. — end note ]

Not sure about this particular case, but my understanding of the quote is that, if after the overload resolution in §12.8/32 the deleted copy/move constructor is selected, even if the operation is elided, that could constitute a reference to the function, and the program would be ill formed.

沉默的熊 2024-12-19 17:07:54

上面的代码在 C++11 中仍然是格式错误的。但是你可以向 A 添加一个公共移动构造函数,这样它就合法了:

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

A f() {
  return A(10); // Ok!
}

The above code is still ill-formed in C++11. But you could add a public move constructor to A and then it would be legal:

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

A f() {
  return A(10); // Ok!
}
醉殇 2024-12-19 17:07:54

我想知道,C++11 中取消了这个限制吗?

怎么可能呢?通过按值返回某些内容,根据定义,您正在复制(或移动)它。虽然 C++ 可以允许在某些情况下省略复制/移动,但它仍然按照规范进行复制(或移动)。

我记得允许函数的调用者使用返回的对象可能很有用,但他们无法复制该值并将其存储在某个地方。

是的。您摆脱了复制构造函数/赋值,但允许移动值std::unique_ptr 就是这样做的。

您可以按值返回unique_ptr。但这样做时,您将返回一个“纯右值”:一个正在被销毁的临时值。因此,如果你有一个这样的函数g

std::unique_ptr<SomeType> g() {...}

你可以这样做:

std::unique_ptr<SomeType> value = g();

但不是this

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 的指针或其他方式),但内存中最多只有一个位置存储实际指针。

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

I was wondering, was this restriction lifted in C++11?

How could it be? By returning something by value, you are by definition copying (or moving) it. And while C++ can allow that copy/move to be elided in certain circumstances, it's still copying (or moving) by the specification.

I remember it could be useful to allow callers of a function use the returned object, but that they are not able to copy the value and store it somewhere.

Yes. You get rid of the copy constructor/assignment, but allow the value to be moved. std::unique_ptr does this.

You can return a unique_ptr by value. But in doing so, you are returning an "prvalue": a temporary that is being destroyed. Therefore, if you have a function g as such:

std::unique_ptr<SomeType> g() {...}

You can do this:

std::unique_ptr<SomeType> value = g();

But not this:

std::unique_ptr<SomeType> value1 = g();
std::unique_ptr<SomeType> value2 = g();
value1 = value 2;

But this is possible:

std::unique_ptr<SomeType> value = g();
value = g();

The second line invokes the move assignment operator on value. It will delete the old pointer and move the new pointer into it, leaving the old value empty.

In this way, you can ensure that the contents of any unique_ptr is only ever stored in one place. You can't stop them from referencing it in multiple places (via pointers to unique_ptr or whatever), but there will be at most one location in memory where the actual pointer is stored.

Removing both the copy and move constructors creates an immobile object. Where it is created is where it's values stay, forever. Movement allows you to have unique ownership, but without being immobile.

伴随着你 2024-12-19 17:07:54

如果您确实想要的话,您可能可以将代理组合在一起来实现这一目的,并有一个转换构造函数来复制代理中存储的值。

不过,

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 执行相同的操作。显然,它不会阻止存储结果对象,尽管它确实限制每个实例只能保存一份副本。

You could probably hack together a proxy to do the trick if you really wanted, and have a converting constructor that copies the value stored within the proxy.

Something along the lines of:

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());
}

You could probably do the same in 03, though, using auto_ptrs. And it obviously doesn't prevent storage of the resultant Object, although it does limit you to one copy per instance.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文