c++ 如果返回指针或引用,成员方法是否应该是常量

46qrfjad  于 2023-04-01  发布在  其他
关注(0)|答案(3)|浏览(100)

我经常陷入困境,是否应该让一个成员方法常量或没有的情况下,这两个选项都工作得很好,并产生完全相同的结果。
考虑以下两个示例类:

class A
{
public:
    /* Valid const correctness */
    int get() const { return x; };

private:
    int x = 0;
};

class B
{
public:
    /* Questionable const correctness */
    int* get() const { return x; };

private:
    int* x = nullptr;
};

int main()
{
    B b;
    int* x = b.get();

    // This defeats the purpose of const keyword
    x = new int(1);
}

A是const正确性的一个很好的例子;但是,B类是有问题的:get()成员函数是否应该标记为const是有疑问的。
const应该暗示子对象不会被修改,如果函数修改其成员,则会导致编译时错误。
在第一个例子中,函数不修改它的成员,调用者也不能修改它;然而,在第二个例子中,调用者可以修改指针,这使得const关键字有问题。
我努力尽可能地使const正确,但在这些情况下我不确定,所以我的问题是,什么才是好的代码设计?如果返回一个调用者可以修改的引用或指针,是否放置const关键字?

hgqdbh6s

hgqdbh6s1#

你的问题是关于“返回一个指针或引用”和const-成员函数的正确性。但是对于指针(由copy 1返回)和引用的情况是非常不同的。
通过引用返回的getter的无效const正确性问题是一个重要问题。
下面的代码有'proper' const-correctness,无论我们将T作为int还是int*

using T = int;

class Foo {
public:
    T get() const { return x; }
private:
    T x = 0;
};

int main()
{
    Foo f;
    T x = f.get();
}

然而,当返回一个 reference 到一个数据成员时,该数据成员在任何返回它的函数中都被认为是const(除非它被声明为mutable),所以下面的代码将无法编译:

using T = int;

class Foo {
public:
    T& get() const { return x; }
private:
    T x = 0;
};

这将生成如下代码行沿着编译器错误(来自clang-cl):
错误:将类型'T'(aka 'int')的引用绑定到类型'const T'(aka 'const int')的值会删除'const'限定符
在后一种情况下,我们必须还将const限定符添加到返回类型中:

using T = int;

class Foo {
public:
    const T& get() const { return x; }
private:
    T x = 0;
};

此外,返回的const引用不能分配给非常量引用,因此使用这些const限定符将阻止调用者修改私有数据成员:

int main()
{
    Foo f;
//  T& x = f.get(); // Illegal
    const T& x = f.get(); // OK
//  x = 42; // Illegal
}

(Note你也可以在我展示的第一个代码片段中添加前导const限定符-但这是多余的,因为get()表达式永远都是右值,无论如何都不能直接修改。
1这里,我讨论的是一个本身就是指针的数据成员(并且通过复制返回)。然而,在getter返回数据成员的地址的情况下,上面关于引用类型的讨论以非常相似的方式应用,并且下面的代码在所需的const-correctness方面是“等效的”:

using T = int;

class Foo {
public:
    const T* get() const { return &x; } // MUST return a const T*
private:
    T x = 0;
};

int main()
{
    Foo f;
//  T* x = f.get(); // Illegal
    const T* x = f.get(); // OK
//  *x = 42; // Illegal
}
xlpyo6sf

xlpyo6sf2#

只要有可能,就创建方法const。在你的例子中,这是可能的,因为const方法创建了const B* this,因此int* const x。你不能将x改为指向另一个int,但可以返回int*

int* const x = nullptr;
int* y = x; // legal

// Equivalent:
using T = int*;
T const x1 = nullptr; // const T x1 = nullptr;
T y1 = x1; // legal

// Even more simple equivalent:
const int x2 = 0;
int y2 = x2; // legal
p8ekf7hl

p8ekf7hl3#

DR,这取决于。
对于指针,有两个独立的常量概念。

int const * x; // pointed-to int is const
Int * const x; // the pointer itself is const

智能指针也是如此,例如const unique_ptr<int>unique_ptr<const int>
原则上可以创建一个“深度常量”智能指针,其中指针本身的常量意味着指向对象的常量,但这样的智能指针很少有用。它们在标准库中找不到。
另一方面,容器通常具有深度恒定性。
所以你应该问问自己:我的类是更像指针还是更像容器?
如果它更像一个指针,那么一个get() const返回一个非常量指针是可以的。
如果它更像一个容器,那么有两个重载的get()

int * get();
int const * get() const;

关于引用的同样的推理也是有效的。没有智能引用,但是有引用 Package 器,它们或多或少地充当指针。

相关问题