链接时如何取消副本?


10

我正在创建一个链接类型的类,例如下面的小示例。似乎在链接成员函数时,将调用复制构造函数。有没有办法摆脱复制构造函数调用?在下面的玩具示例中,很明显,我只在处理临时工,因此“应该”(也许不是按标准,而是从逻辑上)是一种省略。复制省略的第二个最佳选择是调用move构造函数,但事实并非如此。

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

编辑:一些澄清。1.这不取决于优化级别。2.在我的实际代码中,我有比ints更复杂(例如,分配堆)的对象


您使用哪个优化级别进行编译?
JVApen '19

2
auto b = test_class{7};不会调用拷贝构造函数,因为它实际上等同于拷贝构造函数,test_class b{7};并且编译器足够聪明,可以识别这种情况,因此可以轻松消除任何拷贝。不能做同样的事情b2
一些程序员花花公子

在所示的示例中,移动和复制之间可能没有任何实际差异或太多差异,并且并非所有人都意识到这一点。如果您在其中插入了诸如大向量之类的东西,那就可能是另一回事了。通常,移动仅对使用类型的资源有意义(例如,使用大量堆内存等)-在这种情况下吗?
darune

该示例看起来很虚构。std::cout复印控制器中是否确实有I / O()?没有它,副本应该被优化掉。
rustyx

@rustyx,删除std :: cout并使副本构造函数显式。这说明复制省略不依赖于std :: cout。
DDaniel

Answers:


7
  1. 部分答案(它没有b2就位构造,但是将副本构造转变为移动构造):您可以increment在关联实例的值类别上重载成员函数:

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    这引起

    auto b2 = test_class{7}.increment();

    移动是b2因为test_class{7}是临时的,所以调用的&&重载test_class::increment

  2. 对于真正的就地构造(即甚至不是移动构造),您可以将所有特殊和非特殊成员函数转换为constexpr版本。那你就可以

    constexpr auto b2 = test_class{7}.increment();

    而您既不需要搬家,也不需要复制结构。显然,对于简单的test_class,但对于不允许constexpr成员函数的更一般的情况,这是可行的。


第二种选择也使之b2不可修改。
一些程序员花花公子

1
@Someprogrammerdude好点。我想那constinit是去那里的方式。
lubgr

函数名称后面的“&”号的作用是什么?我以前从未见过。
菲利普·尼尔森

1
@PhilipNelson他们是裁判限定词,可以指示thisconst成员函数相同的
事物

1

基本上,分配到一个,需要调用一个构造,即,复制移动。这与不同,后者在函数的两面都知道是相同的不同对象。还一个可指代共享对象很像一个指针。


最简单的方法可能是使副本构造函数得到完全优化。值设置已由编译器优化,只是std::cout无法优化。

test_class(const test_class& t) = default;

(或只删除copy和move构造函数)

现场例子


由于您的问题基本上与引用有关,因此,如果您要停止以这种方式进行复制,则解决方案可能不会返回对对象的引用。

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

第三种方法只是首先依赖复制省略 -但这需要将操作重写或封装到一个函数中,从而完全避免了该问题(我知道这可能不是您想要的,但可能是其他用户的解决方案):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

第四种方法是使用移动,而是显式调用

auto b2 = std::move(test_class{7}.increment());

如看到这个答案


@ O'Neil正在考虑另一个案件
-ty
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.