C++:通过引用和复制构造函数返回

发布于 2024-08-22 06:32:51 字数 943 浏览 5 评论 0原文

C++ 中的引用让我感到困惑。 :)

基本思想是我试图从函数返回一个对象。我想在不返回指针的情况下执行此操作(因为那样我必须手动删除它),并且如果可能的话,不调用复制构造函数(为了提高效率,自然添加:也因为我想知道我是否无法避免编写复制构造函数)。

因此,总而言之,以下是我发现的执行此操作的选项:

  • 函数返回类型可以是类本身 (MyClass fun() { ... }) 或对类 (MyClass& fun() { ... })。
  • 该函数可以在返回行构造变量 (return MyClass(a,b,c);) 或返回现有变量 (MyClass x(a,b,c);返回 x;)。
  • 接收变量的代码也可以具有以下任一类型的变量: (MyClass x = fun();MyClass& x = fun();
  • )接收变量可以动态创建一个新变量 (MyClass x = fun();) 或将其分配给现有变量 (MyClass x; x = fun(); code>)

对此有一些想法:

  • 使用返回类型 MyClass& 似乎是一个坏主意,因为这总是会导致变量在返回之前被销毁。
  • 复制构造函数似乎只在我返回现有变量时才会参与。当返回在返回行中构造的变量时,它永远不会被调用。
  • 当我将结果分配给现有变量时,析构函数也总是在返回值之前启动。此外,没有调用复制构造函数,但目标变量确实接收从函数返回的对象的成员值。

这些结果是如此不一致,让我感到完全困惑。那么,这里究竟发生了什么?我应该如何正确构造并从函数返回对象?

References in C++ are baffling me. :)

The basic idea is that I'm trying to return an object from a function. I'd like to do it without returning a pointer (because then I'd have to manually delete it), and without calling the copy-constructor, if possible (for efficiency, naturally added: and also because I wonder if I can't avoid writing a copy constructor).

So, all in all, here are the options for doing this that I have found:

  • The function return type can be either the class itself (MyClass fun() { ... }) or a reference to the class (MyClass& fun() { ... }).
  • The function can either construct the variable at the line of return (return MyClass(a,b,c);) or return an existing variable (MyClass x(a,b,c); return x;).
  • The code that receives the variable can also have a variable of either type: (MyClass x = fun(); or MyClass& x = fun();)
  • The code which receives the variable can either create a new variable on the fly (MyClass x = fun();) or assign it to an existing variable (MyClass x; x = fun();)

And some thoughts on that:

  • It seems to be a bad idea to have the return type MyClass& because that always results in the variable being destroyed before it gets returned.
  • The copy constructor only seems to get involved when I return an existing variable. When returning a variable constructed in the line of return, it never gets called.
  • When I assign the result to an existing variable, the destructor also always kicks in before the value is returned. Also, no copy constructor gets called, yet target variable does receive the member values of the object returned from the function.

These results are so inconsistent that I feel totally confused. So, what EXACTLY is happening here? How should I properly construct and return an object from a function?

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

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

发布评论

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

评论(9

荭秂 2024-08-29 06:32:51

理解 C++ 中复制的最佳方法通常不是尝试生成一个人工示例并对其进行检测 - 允许编译器删除和添加复制构造函数调用,或多或少根据它认为合适。

底线 - 如果您需要返回一个值,就返回一个值,不要担心任何“费用”。

The best way to understand copying in C++ is often NOT to try to produce an artificial example and instrument it - the compiler is allowed to both remove and add copy constructor calls, more or less as it sees fit.

Bottom line - if you need to return a value, return a value and don't worry about any "expense".

じее 2024-08-29 06:32:51

推荐阅读:Effective C++ 作者:Scott迈耶斯。您可以在那里找到关于这个主题(以及更多)的非常好的解释。

简而言之,如果您按值返回,则默认情况下将涉及复制构造函数和析构函数(除非编译器将它们优化掉 - 这就是在某些情况下发生的情况)。

如果通过引用(或指针)返回本地变量(在堆栈上构造),则会带来麻烦,因为该对象在返回时被破坏,因此结果是悬空引用。

在函数中构造对象并返回它的规范方法是按值,例如:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

如果使用它,则无需担心所有权问题、悬空引用等。并且编译器很可能会优化额外的副本构造函数/析构函数会为您调用,因此您也无需担心性能。

可以通过引用返回由 new 构造的对象(即在堆上) - 从函数返回时该对象不会被销毁。但是,您必须稍后通过调用删除来显式销毁它。

从技术上讲,也可以将按值返回的对象存储在引用中,例如:

MyClass& x = fun();

但是,据我所知,这样做没有多大意义。特别是因为人们可以轻松地将这一引用传递给当前范围之外的程序其他部分;但是,x 引用的对象是本地对象,一旦离开当前作用域,该对象就会被销毁。所以这种风格可能会导致严重的错误。

Recommended reading: Effective C++ by Scott Meyers. You find a very good explanation about this topic (and a lot more) in there.

In brief, if you return by value, the copy constructor and the destructor will be involved by default (unless the compiler optimizes them away - that's what happens in some of your cases).

If you return by reference (or pointer) a variable which is local (constructed on the stack), you invite trouble because the object is destructed upon return, so you have a dangling reference as a result.

The canonical way to construct an object in a function and return it is by value, like:

MyClass fun() {
    return MyClass(a, b, c);
}

MyClass x = fun();

If you use this, you don't need to worry about ownership issues, dangling references etc. And the compiler will most likely optimize out the extra copy constructor / destructor calls for you, so you don't need to worry about performance either.

It is possible to return by reference an object constructed by new (i.e. on the heap) - this object will not be destroyed upon returning from the function. However, you have to destroy it explicitly somewhere later by calling delete.

It is also technically possible to store an object returned by value in a reference, like:

MyClass& x = fun();

However, AFAIK there is not much point in doing this. Especially because one can easily pass on this reference to other parts of the program which are outside of the current scope; however, the object referenced by x is a local object which will be destroyed as soon as you leave the current scope. So this style can lead to nasty bugs.

困倦 2024-08-29 06:32:51

阅读有关 RVO 和 NRVO(总而言之,这两个代表返回值优化和命名 RVO,以及是编译器用来完成您想要实现的目标的优化技术)

您会在 stackoverflow 上找到很多主题

read about RVO and NRVO (in a word these two stands for Return Value Optimization and Named RVO, and are optimization techniques used by the compiler to do what you're trying to achieve)

you'll find a lot of subjects here on stackoverflow

还在原地等你 2024-08-29 06:32:51

如果您创建这样的对象:

MyClass foo(a, b, c);

那么它将位于函数框架的堆栈上。当该函数结束时,其帧将从堆栈中弹出,并且该帧中的所有对象都将被销毁。 没有办法避免这种情况。

因此,如果您想将对象返回给调用者,您唯一的选择是:

  • 按值返回 - 需要复制构造函数(但对复制构造函数的调用可能会被优化掉)。
  • 返回一个指针,并确保您使用智能指针来处理它,或者在使用完它后小心地自行删除它。

尝试构造本地对象,然后将对该本地内存的引用返回给调用上下文是不连贯的 - 调用作用域无法访问被调用作用域的本地内存。该本地内存仅在拥有它的函数的持续时间内有效 - 或者,另一种方式,当执行保留在该范围内时。您必须理解这一点才能使用 C++ 进行编程。

If you create an object like this:

MyClass foo(a, b, c);

then it will be on the stack in the function's frame. When that function ends, its frame is popped off the stack and all the objects in that frame are destructed. There is no way to avoid this.

So if you want to return an object to a caller, you only options are:

  • Return by value - a copy constructor is required (but the call to the copy constructor may be optimised out).
  • Return a pointer and make sure you either use smart pointers to deal with it or carefully delete it yourself when done with it.

Attempting to construct a local object and then return a reference to that local memory to a calling context is not coherent - a calling scope can not access memory that is local to the called scope. That local memory is only valid for the duration of the function that owns it - or, another way, while execution remains in that scope. You must understand this to program in C++.

你在我安 2024-08-29 06:32:51

返回引用的唯一有意义的情况是返回对预先存在的对象的引用。举一个明显的例子,几乎每个 iostream 成员函数都会返回对 iostream 的引用。 iostream 本身在调用任何成员函数之前就存在,并在调用它们之后继续存在。

该标准允许“复制省略”,这意味着返回对象时不需要调用复制构造函数。这有两种形式:名称返回值优化 (NRVO) 和匿名返回值优化(通常只是 RVO)。

根据您的说法,您的编译器实现了 RVO,但没有实现 NRVO——这意味着它可能是一个有点旧的编译器。当前大多数编译器都实现了两者。在这种情况下,不匹配的 dtor 意味着它可能类似于 gcc 3.4 或类似版本——虽然我不太记得版本,但当时有一个版本有这样的错误。当然,也有可能您的检测不太正确,因此正在使用您未检测的 ctor,并且正在为该对象调用匹配的 dtor。

最后,您仍然坚持一个简单的事实:如果您需要返回一个对象,则需要返回一个对象。特别是,引用只能访问现有对象(可能是其修改版本)——但该对象也必须在某个时刻构造。如果您可以修改某些现有对象而不会引起问题,那就很好,那就继续吧。如果您需要一个与已有对象不同且分开的新对象,请继续执行此操作 - 预先创建该对象并传入对其的引用可能会使返回本身更快,但总体上不会节省任何时间。无论是在函数内部还是在函数外部创建对象的成本大致相同。任何相当现代的编译器都会包含 RVO,因此您无需为在函数中创建它然后返回它而支付任何额外成本 - 编译器只会自动为要返回的对象分配空间,并让该函数“就地”构造它,在函数返回后仍然可以访问它。

About the only time it makes sense to return a reference is if you're returning a reference to a pre-existing object. For an obvious example, nearly every iostream member function returns a reference to the iostream. The iostream itself exists before any of the member functions is called, and continues to exist after they're called.

The standard allows "copy elision", which means the copy constructor doesn't need to be called when you return an object. This comes in two forms: Name Return Value Optimization (NRVO) and anonymous Return Value Optimization (usually just RVO).

From what you're saying, your compiler implements RVO but not NRVO -- which means it's probably a somewhat older compiler. Most current compilers implement both. The un-matched dtor in this case means it's probably something like gcc 3.4 or thereabouts -- though I don't remember the version for sure, there was a one around then that had a bug like this. Of course, it's also possible that your instrumentation isn't quite right, so a ctor that you didn't instrument is being used, and a matching dtor is being invoked for that object.

In the end, you're stuck with one simple fact though: if you need to return an object, you need to return an object. In particular, a reference can only give access to a (possibly modified version of) an existing object -- but that object had to be constructed at some point as well. If you can modify some existing object without causing a problem, that's fine and well, go ahead and do it. If you need a new object different and separate from those you already have, go ahead and do that -- pre-creating the object and passing in a reference to it may make the return itself faster, but won't save any time overall. Creating the object has about the same cost whether done inside or outside the function. Any reasonably modern compiler will include RVO, so you won't pay any extra cost for creating it in the function, then returning it -- the compiler will just automate allocating space for the object where it's going to be returned, and have the function construct it "in place", where it'll still be accessible after the function returns.

奢华的一滴泪 2024-08-29 06:32:51

基本上,仅当对象在离开方法后仍然存在时,返回引用才有意义。如果您返回对正在被销毁的内容的引用,编译器会警告您。

返回引用而不是按值返回对象可以节省复制对象的时间,这可能很重要。

引用比指针更安全,因为它们具有不同的语义,但在幕后它们是指针。

Basically, returning a reference only makes sense if the object still exists after leaving the method. The compiler will warn you if you return a reference to something that is being destroyed.

Returning a reference rather than an object by value saves copying the object which might be significant.

References are safer than pointers because they have different symantics, but behind the scenes they are pointers.

逆流 2024-08-29 06:32:51

根据您的用例,一种潜在的解决方案是在函数外部默认构造对象,获取对其的引用,并在函数内初始化引用的对象,如下所示

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

:当然,如果不可能(或没有意义)默认构造一个 Foo 对象并稍后初始化它,那么当然不起作用。如果是这种情况,那么避免复制构造的唯一真正选择是返回指向堆分配对象的指针。

但然后想想为什么你首先要试图避免复制构造。复制构造的“费用”真的会影响你的程序吗,还是这是一种过早优化的情况?

One potential solution, depending on your use case, is to default-construct the object outside of the function, take in a reference to it, and initialize the referenced object within the function, like so:

void initFoo(Foo& foo) 
{
  foo.setN(3);
  foo.setBar("bar");
  // ... etc ...
}

int main() 
{
  Foo foo;
  initFoo(foo);

  return 0;
}

Now this of course does not work if it is not possible (or does not make sense) to default-construct a Foo object and then initialize it later. If that is the case, then your only real option to avoid copy-construction is to return a pointer to a heap-allocated object.

But then think about why you are trying to avoid copy-construction in the first place. Is the "expense" of copy construction really affecting your program, or is this a case of premature optimization?

南烟 2024-08-29 06:32:51

您陷入困境:

1)返回指针

MyClass* func(){
//一些东西
返回新的 MyClass(a,b,c);
2

) 返回对象的副本
我的类函数(){
返回 MyClass(a,b,c);
返回

引用无效,因为该对象在退出 func 作用域后将被销毁,除非该函数是该类的成员并且引用来自该类成员的变量。

You are stucked with either:

1) returning a pointer

MyClass* func(){
//some stuf
return new MyClass(a,b,c);
}

2) returning a copy of the object
MyClass func(){
return MyClass(a,b,c);
}

Returning a reference is not valid because the object is to be destroyed after exiting the func scope, except if the function is a member of the class and the reference is from a variable that is member of the class.

吃颗糖壮壮胆 2024-08-29 06:32:51

不是直接的答案,而是一个可行的建议:您还可以返回一个包含在 auto_ptr 或 smart_ptr 中的指针。然后您将可以控制调用哪些构造函数和析构函数以及何时调用。

Not a direct answer, but a viable suggestion: You could also return a pointer, wrapped in an auto_ptr or smart_ptr. Then you'll be in control of what constructors and destructors get called and when.

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