对于非内置类型,使用const值返回函数有什么用例?

Gra*_*eme 40 c++ const-correctness

最近我读过,从函数返回值来为非内置类型限定返回类型const是有意义的,例如:

const Result operation() {
    //..do something..
    return Result(..);
}
Run Code Online (Sandbox Code Playgroud)

我很难理解这个的好处,一旦对象被返回肯定是调用者的选择来决定返回的对象是否应该是const?

Pup*_*ppy 37

基本上,这里有一个轻微的语言问题.

std::string func() {
    return "hai";
}

func().push_back('c'); // Perfectly valid, yet non-sensical
Run Code Online (Sandbox Code Playgroud)

返回const rvalues是为了防止这种行为.然而,实际上,它确实弊大于利,因为现在rvalue引用就在这里,你只是要防止移动语义,这很糟糕,并且通过明智地使用rvalue和lvalue可能会阻止上述行为*this超载.另外,无论如何,你必须要做一点白痴.

  • 对于`void`方法来说,这是荒谬的.对于返回某些东西的方法,它可以很有意义.例如,如果`string :: push_back`返回对字符串本身的引用,则可以执行`string s = func().push_back('c');`(例如). (2认同)

Joh*_*eek 13

它偶尔会有用.看这个例子:

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;
}
Run Code Online (Sandbox Code Playgroud)

请注意,返回的值operator+通常被视为临时值.但它显然正在被修改.这不是完全需要的.

如果声明返回类型为operator+as const I,则无法编译.

  • “有效的C ++”中的第3项 (2认同)

Pet*_*der 11

按价值返回没有任何好处.这没有意义.

唯一的区别是它阻止人们将它用作左值:

class Foo
{
    void bar();
};

const Foo foo();

int main()
{
    foo().bar(); // Invalid
}
Run Code Online (Sandbox Code Playgroud)

  • 这实际上是不正确的.它并不妨碍人们将它用作左值.它可以防止人们将它用作非const对象.这是两件完全不同的事情. (3认同)

yep*_*ons 5

去年,我在研究双向 C++ 到 JavaScript 绑定时发现了另一个令人惊讶的用例。

它需要以下条件的组合:

  • 您有一个可复制且可移动的类Base
  • Derived您有一个源自的不可复制、不可移动的类Base
  • Base你真的、真的不希望inside的实例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());
}
Run Code Online (Sandbox Code Playgroud)

事实上,我没有指定 的类型参数,这oops_use<>意味着编译器应该能够从参数的类型中推断出它,因此要求Base<T>实际上是Derived<T>.

调用时应该发生隐式转换oops_use(Base<T>)。为此,create_derived()的结果被具体化为临时Derived<T>值,然后oops_use通过移动构造函数将其移动到 的参数中Base<T>(Base<T>&&)。因此,物化临时对象现已被移出,并且断言失败。

我们不能删除该移动构造函数,因为它将变得Base<T>不可移动。我们无法真正阻止Base<T>&&绑定到Derived<T>&&(除非我们显式删除Base<T>(Derived<T>&&),这应该对所有派生类执行)。

因此,这里唯一未经Base修改的解决方案是返回create_derived()return const Derived<T>,以便该oops_use参数的构造函数无法从物化临时对象中移动。

我喜欢这个例子,因为它不仅在有和没有的情况下编译const,没有任何未定义的行为,而且在有和没有的情况下它的行为不同const,而且正确的行为实际上const只发生在。