是否有移动 const 对象的有用场景?
Is there a useful scenario for moving const objects?
我意识到 "you cannot move a const object" 的常识并不完全正确。你可以,如果你声明移动构造函数为
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)};
}
问题:有人想要这样的东西有什么理由吗?任何 useful/practical 场景?
正如评论所指出的,您实际上不能 "move" 参数对象之外的任何东西,因为它是 const(至少,不是没有 const 转换,这是一个坏主意,因为它可能导致UB)。所以为了搬家显然是没有用的。移动语义的全部目的是提供性能优化,而这并没有发生在这里,那为什么要这样做呢?
也就是说,我只能想到两种有用的情况。第一个涉及 "greedy" 个构造函数:
#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;
}
此程序打印 "forward"。原因是因为模板中的推导类型将是 const Foo
,使其更匹配。当您拥有完美的转发可变参数构造函数时,这也会出现。代理对象通用。当然,通过 const 值返回是一种不好的做法,但严格来说这并没有错,而且这可能会破坏你的 class。所以你真的应该提供一个 Foo(const Foo&&)
重载(它只是委托给复制构造函数);当你在编写高质量的通用代码时,可以把它想象成穿过一个 t 或点一个 i。
第二种情况发生在你想显式删除移动构造函数或移动转换运算符时:
struct Baz {
Baz() = default;
Baz(const Baz&) = default;
Baz(Baz&&) = delete;
};
const Baz zwug() { return {}; }
int main() {
Baz b2(zwug());
}
该程序编译成功,因此作者未能完成任务。原因是因为 const ref 重载与 const 右值匹配,并且 const 右值构造未明确删除。如果你想删除移动,你也需要删除 const 右值重载。
第二个示例可能看起来非常晦涩,但假设您正在编写一个提供字符串视图的 class。您可能不想允许它从临时字符串构造,因为您面临更大的视图损坏风险。
你的例子没有任何意义。是的,你写了 std::move
来获得一个右值并且你调用了一个移动构造函数,但实际上没有任何东西最终被移动。它不能,因为对象是 const
.
除非您感兴趣的成员被标记为 mutable
,否则您将无法执行任何操作 "moving"。所以,没有有用甚至可能的场景。
不确定是否实用,但只要修改的数据成员是 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
我意识到 "you cannot move a const object" 的常识并不完全正确。你可以,如果你声明移动构造函数为
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)};
}
问题:有人想要这样的东西有什么理由吗?任何 useful/practical 场景?
正如评论所指出的,您实际上不能 "move" 参数对象之外的任何东西,因为它是 const(至少,不是没有 const 转换,这是一个坏主意,因为它可能导致UB)。所以为了搬家显然是没有用的。移动语义的全部目的是提供性能优化,而这并没有发生在这里,那为什么要这样做呢?
也就是说,我只能想到两种有用的情况。第一个涉及 "greedy" 个构造函数:
#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;
}
此程序打印 "forward"。原因是因为模板中的推导类型将是 const Foo
,使其更匹配。当您拥有完美的转发可变参数构造函数时,这也会出现。代理对象通用。当然,通过 const 值返回是一种不好的做法,但严格来说这并没有错,而且这可能会破坏你的 class。所以你真的应该提供一个 Foo(const Foo&&)
重载(它只是委托给复制构造函数);当你在编写高质量的通用代码时,可以把它想象成穿过一个 t 或点一个 i。
第二种情况发生在你想显式删除移动构造函数或移动转换运算符时:
struct Baz {
Baz() = default;
Baz(const Baz&) = default;
Baz(Baz&&) = delete;
};
const Baz zwug() { return {}; }
int main() {
Baz b2(zwug());
}
该程序编译成功,因此作者未能完成任务。原因是因为 const ref 重载与 const 右值匹配,并且 const 右值构造未明确删除。如果你想删除移动,你也需要删除 const 右值重载。
第二个示例可能看起来非常晦涩,但假设您正在编写一个提供字符串视图的 class。您可能不想允许它从临时字符串构造,因为您面临更大的视图损坏风险。
你的例子没有任何意义。是的,你写了 std::move
来获得一个右值并且你调用了一个移动构造函数,但实际上没有任何东西最终被移动。它不能,因为对象是 const
.
除非您感兴趣的成员被标记为 mutable
,否则您将无法执行任何操作 "moving"。所以,没有有用甚至可能的场景。
不确定是否实用,但只要修改的数据成员是 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