移动const对象是否有用?

时间:2017-04-18 19:06:07

标签: c++ c++11 move-semantics

我意识到“你不能移动const对象”的常识并不完全正确。如果您将移动ctor声明为

,则可以
X(const X&&);

以下完整示例:

#include <iostream>

struct X
{
    X() = default;
    X(const X&&) {std::cout << "const move\n";}
};

int main()
{
    const X x{};
    X y{std::move(x)};
}

Live on Coliru

问题:有什么理由让人想要这样的事情吗?任何有用/实用的场景?

3 个答案:

答案 0 :(得分:2)

你的例子没有移动任何东西。是的,你写了std::move来获得一个rvalue并且你调用了一个移动构造函数,但实际上没有任何东西被移动。它不能,因为对象是const

除非您感兴趣的成员被标记为mutable,否则您将无法进行任何“移动”。因此,没有有用甚至可能的情况。

答案 1 :(得分:2)

不确定它是否实用,但如果修改后的数据成员为mutable,则可以合法。

这个程序是合法的,如果你喜欢这样的东西,很容易变得难以理解:

#include <iostream>
#include <string>

struct animal
{
    animal(const animal&& other) : type(other.type) {
        other.type = "dog";
    }
    animal() = default;

    mutable std::string type = "cat";
};

std::ostream& operator<<(std::ostream& os, const animal& a)
{
    return os << "I am a " << a.type;
}
std::ostream& operator<<(std::ostream& os, const animal&& a)
{
    return os << "I am a " << a.type << " and I feel moved";
}

int main()
{
    const auto cat = animal();
    std::cout << cat << std::endl;

    auto dog = std::move(cat);
    std::cout << cat << std::endl;

    std::cout << dog << std::endl;
    std::cout << std::move(dog) << std::endl;
}

预期产出:

I am a cat
I am a dog
I am a cat
I am a cat and I feel moved

答案 2 :(得分:1)

正如评论所指出的那样,你实际上无法移动&#34;任何超出参数对象的东西,因为它是const(至少,没有const cast,这是一个坏主意,因为它可能导致UB)。因此,为了移动它显然没有用。移动语义的整个目的是提供性能优化,这不会发生在这里,为什么要这样做呢?

那就是说,我只能想到两个有用的案例。第一个涉及&#34;贪心&#34;构造函数:

#include <iostream>

struct Foo {
    Foo() = default;
    Foo(const Foo&) { std::cerr << "copy constructor"; }
    Foo(Foo&&) { std::cerr << "copy constructor"; }

    template <class T>
    Foo(T&&) { std::cerr << "forward"; }      
};

const Foo bar() { return Foo{}; }

int main() {
    Foo f2(bar());        
    return 0;   
}

此程序打印&#34;转发&#34;。原因是因为模板中的推导类型将为const Foo,使其更好地匹配。当你有完美的转发可变参数构造函数时,这也会显示出来。代理对象的常见问题。当然,使用const值返回是不好的做法,但严格来说这并不是错误的,这可能会打破你的课程。所以你应该提供一个Foo(const Foo&&)重载(它只是委托给复制构造函数);当你在编写高质量的通用代码时,可以把它想象成穿过或点缀i。

如果要显式删除移动构造函数或移动转换运算符,则会出现第二种情况:

struct Baz {
    Baz() = default;
    Baz(const Baz&) = default;
    Baz(Baz&&) = delete;
};

const Baz zwug() { return {}; }

int main() {
    Baz b2(zwug());
}

这个程序汇编,因此作者在任务中失败了。原因是因为const ref重载与const rvalues匹配,并且未明确删除const rvalue构造。如果你想删除移动,你也需要删除const rvalue重载。

第二个例子可能看起来非常模糊,但是你说你正在编写一个提供字符串视图的类。您可能不希望允许它从字符串临时构造,因为您更有可能损坏视图。