最近我读过,从函数返回值来限定非内置类型的返回类型const是有意义的,例如:
const Result operation() {
//..do something..
return Result(..);
}
我很难理解这个的好处,一旦返回了对象,确定调用者的选择是决定返回的对象是否应该是const?
答案 0 :(得分:34)
基本上,这里有轻微的语言问题。
std::string func() {
return "hai";
}
func().push_back('c'); // Perfectly valid, yet non-sensical
返回const rvalues是为了防止这种行为。然而,实际上,它确实弊大于利,因为现在rvalue引用就在这里,你只是要防止移动语义,这很糟糕,并且通过明智地使用rvalue和lvalue可能会阻止上述行为*this
重载。另外,无论如何,你必须要做一点白痴。
答案 1 :(得分:12)
偶尔会有用。见这个例子:
class I
{
public:
I(int i) : value(i) {}
void set(int i) { value = i; }
I operator+(const I& rhs) { return I(value + rhs.value); }
I& operator=(const I& rhs) { value = rhs.value; return *this; }
private:
int value;
};
int main()
{
I a(2), b(3);
(a + b) = 2; // ???
return 0;
}
请注意,operator+
返回的值通常被视为临时值。但它显然正在被修改。这不是完全想要的。
如果将operator+
的返回类型声明为const I
,则无法编译。
答案 2 :(得分:9)
按价值返回没有任何好处。这没有意义。
唯一的区别是它阻止人们将它用作左值:
class Foo
{
void bar();
};
const Foo foo();
int main()
{
foo().bar(); // Invalid
}
答案 3 :(得分:0)
去年,我在处理双向C ++到JavaScript绑定时发现了另一个令人惊讶的用例。
它需要以下条件的组合:
'*': (a, b) => a * b,
'/': (a, b) => a / b,
SWAP: (a, b) => [b, a],
ROT: (a, b, c) => [b, c, a],
。Base
派生的不可复制的不可移动类Derived
。Base
中的Base
实例也可移动。Derived
或类似的技巧代替公共继承。Derived::operator const Base&()
我没有为#include <cassert>
#include <iostream>
#include <string>
#include <utility>
// Simple class which can be copied and moved.
template<typename T>
struct Base {
std::string data;
};
template<typename T>
struct Derived : Base<T> {
// Complex class which derives from Base<T> so that type deduction works
// in function calls below. This class also wants to be non-copyable
// and non-movable, so we disable copy and move.
Derived() : Base<T>{"Hello World"} {}
~Derived() {
// As no move is permitted, `data` should be left untouched, right?
assert(this->data == "Hello World");
}
Derived(const Derived&) = delete;
Derived(Derived&&) = delete;
Derived& operator=(const Derived&) = delete;
Derived& operator=(Derived&&) = delete;
};
// assertion fails when the `const` below is commented, wow!
/*const*/ auto create_derived() { return Derived<int>{}; }
// Next two functions hold reference to Base<T>/Derived<T>, so there
// are definitely no copies or moves when they get `create_derived()`
// as a parameter. Temporary materializations only.
template<typename T>
void good_use_1(const Base<T> &) { std::cout << "good_use_1 runs" << std::endl; }
template<typename T>
void good_use_2(const Derived<T> &) { std::cout << "good_use_2 runs" << std::endl; }
// This function actually takes ownership of its argument. If the argument
// was a temporary Derived<T>(), move-slicing happens: Base<T>(Base<T>&&) is invoked,
// modifying Derived<T>::data.
template<typename T>
void oops_use(Base<T>) { std::cout << "bad_use runs" << std::endl; }
int main() {
good_use_1(create_derived());
good_use_2(create_derived());
oops_use(create_derived());
}
指定类型实参的事实意味着编译器应该能够从参数的类型推论得出,因此要求oops_use<>
实际上是{{ 1}}。
调用Base<T>
时应该发生隐式转换。为此,将Derived<T>
的结果具体化为一个临时oops_use(Base<T>)
值,然后通过create_derived()
move构造函数将其移到Derived<T>
的参数中。因此,现在将物化临时对象移出,并且断言失败。
我们无法删除该move构造函数,因为它将使oops_use
不可移动。而且,我们无法真正阻止Base<T>(Base<T>&&)
绑定到Base<T>
(除非我们显式删除Base<T>&&
,这对于所有派生类都应如此)。
因此,此处唯一未经Derived<T>&&
修改的解决方案是使Base<T>(Derived<T>&&)
返回Base
,以便create_derived()
的参数构造函数不能从物化临时变量中移出。 / p>
我喜欢这个示例,因为它不仅在有和没有const Derived<T>
的情况下都进行编译而没有任何未定义的行为,并且在有和没有oops_use
的情况下其行为都不同,并且正确的行为实际上在const
下发生