对于非内置类型,函数通过常量值返回的用例是什么
What are the use cases for having a function return by const value for non-builtin type?
最近我读到,当从函数按值返回时,将返回类型const限定为非内置类型是有意义的,例如:
const Result operation() {
//..do something..
return Result(..);
}
我很难理解这样做的好处,一旦返回了对象,就由调用者来决定返回的对象是否应该是const?
基本上,这里有一个轻微的语言问题。
std::string func() {
return "hai";
}
func().push_back('c'); // Perfectly valid, yet non-sensical
返回常量值是为了防止这种行为。然而,在现实中,它弊大于利,因为现在有了右值引用,你只需要阻止移动语义,这很糟糕,而且明智地使用右值和左值*this
重载可能会阻止上述行为。另外,无论如何,你都得是个白痴。
它偶尔会有用。参见此示例:
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
,则编译失败。
按值返回没有任何好处。这没有道理。
唯一的区别是它阻止人们将其用作左值:
class Foo
{
void bar();
};
const Foo foo();
int main()
{
foo().bar(); // Invalid
}
去年,我在处理双向C++到JavaScript绑定时发现了另一个令人惊讶的用例。
它需要以下条件的组合:
- 您有一个可复制且可移动的类
Base
- 您有一个从
Base
派生的不可复制的不可移动类Derived
- 您真的,真的不希望
Derived
中的Base
实例也可以移动 - 然而,无论出于何种原因,您确实希望切片工作
- 所有的类实际上都是模板,并且您希望使用模板类型推导,所以您不能真正使用
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<>
指定类型参数,这意味着编译器应该能够从参数的类型推导出它,因此要求Base<T>
实际上是Derived<T>
的真正基础。
调用oops_use(Base<T>)
时应进行隐式转换。为此,create_derived()
的结果被具体化为一个临时的Derived<T>
值,然后由Base<T>(Base<T>&&)
移动构造函数将其移动到oops_use
的参数中。因此,物化的临时对象现在从中移出,断言失败。
我们不能删除那个move构造函数,因为它会使Base<T>
不可移动。并且我们不能真正阻止Base<T>&&
绑定到Derived<T>&&
(除非我们显式删除Base<T>(Derived<T>&&)
,这应该对所有派生类执行)。
因此,这里唯一没有修改Base
的解决方案是让create_derived()
返回const Derived<T>
,这样oops_use
的参数的构造函数就不能从物化的临时中移动。
我喜欢这个例子,因为它不仅在有和没有const
的情况下编译,没有任何未定义的行为,而且在有和不有const
的情况下,它的行为也不同,而正确的行为实际上只发生在const
上。
- "throw expression code" 1e7 >返回 d 是什么?投掷标准::overflow_error( "too big" ) : d;意味 着?
- 将传入的网络"char*"数据转换为"uint8_t"并返回的安全方法是什么?
- 返回类型在 C++ OOP 中是什么意思
- 在C++中返回 IO 对象的目的是什么?
- 对于具有引用返回类型的搜索算法,默认返回值应该是什么?
- T*&返回类型到底是什么
- 在 c++ 中从执行的 shell 命令获取返回状态的安全方法是什么?
- 使用作为参数返回的指针的最佳做法是什么
- 返回向量元素的 l 值的正确方法是什么?
- 私有在函数定义/实现的返回值范围内是什么意思 (c++)?
- CUDA返回值错误35的含义是什么
- c++运算符重载-我实际返回的操作数类型是什么
- GetWindowRect()返回的大小小于游戏的实际可见窗口的可能原因是什么
- 运算符和返回类型是什么意思?
- 返回布尔值在 cocos2dx 的 onTouchBegan() 中是什么意思?
- 在C++中为零大小的分配返回唯一地址背后的基本原理是什么?
- 在下面函数的返回中添加 const 限定符的重要性是什么?
- 在C++中将结构从被调用函数返回到调用函数的适当方法是什么
- 是什么意思,返回值是一个类名,后跟一对空括号
- 将函数结果作为"output parameter"返回是什么意思?