C++ - 常量成员 / 返回 const int& vs 返回 int (注:此为提问标题,无需回答)

11

这些 C++ 代码行的含义是什么?是否有其他编写它们的替代方法?

const int& a() const;
int getA() const;

谢谢。

5个回答

13
这是一个承诺不改变对象本身的类成员函数的两个可能的签名。在第一种情况下,它将返回一个整数的常量引用(可能是一个成员属性),引用是const表示调用者将无法使用它来更改内部属性。在第二种情况下,它通过值返回一个整数。
虽然语义上略有差异,但在大多数情况下它们并不重要,可以将它们视为获取值的两个函数。在需要区分的情况下,请参考以下内容:
class test {
public:
   test() : m_value() {
      std::cout << &m_value << std::endl; // print where the attribute is
   }
   int const & getValue() const {
      return m_value;
   }
   int copyValue() const {
      return m_value;
   }
   void setValue( int value ) {
      m_value = value;
   }
private:
   int m_value;
};
int main() {
   test t;                      // will printout an address [1]
   int v1 = t.getValue();       // caller copies the value
   int v2 = t.copyValue();      // caller copies the value (itself a copy in hte calle)
   int const &r = t.getValue(); // reference to t.m_value
   int const &c = t.copyValue();// reference to *copy* [2]
   std::cout << v1 << v2 << r << c
      << std::cout;             // 0000
   std::cout << &v1 << &v2      // 4 pointers, the third is [1] a r *is* t.m_value
      << &r << &c << std::cout; //     the rest should be different
   t.setValue( 5 );
   std::cout << v1 << v2 << r   // 0050, v1 and v2 where copies, r *is* t.m_value
      << c << std::cout;
}

标记为[2]的行使用了语言中的一个奇怪特性,即如果您获得对r值(临时)的常量引用,编译器将绑定该临时对象到引用上,并保持其有效,直到引用超出作用域(基本上它将r值临时变量转换为隐藏变量并将引用绑定到它上面)。

我添加了那一行以明确说明行为差异不是由接收端main进行复制或维护引用造成的,而是由访问器的签名(要精确)造成的。


8

两种方法都是实现相同目的的等效方式:

const int& a() const;
int getA() const;

你正在返回一个值。方法头右侧的const是一个提示,表明函数getA()和a()不会修改执行它们的对象(隐藏的this参数)。这在编译时非常重要,因为它意味着在编译时将考虑额外的验证。在运行时,上述函数和以下函数之间没有区别:
const int& a();
int getA();

然而,扩展编译器验证能力所带来的巨大好处(当您不期望发生更改时不会更改任何内容)显然值得额外使用const

第二个需要担心的部分是两个函数的返回类型,这是它们之间的主要区别,并且可能是问题产生的动机。让我们将话题转换到另一个函数:

std::string getName() const;
   { return name; }

在这里,返回值可能是类的name属性。返回值是按值返回的,这意味着从该方法返回时将创建属性的副本。当字符串很大且您正在通过值移动许多字符串时,这可能会成为一个问题。此时,出现了按引用返回的机制,它承诺不进行复制:

std::string &getName() const
   { return name; }

这实际上非常有趣:我们返回的是一个引用,而不是对象的副本。引用类似于指针,因此您只需复制一个指针(在 32 位系统中为 4 字节),而不是整个对象。

这非常有前途。但是,它甚至都无法编译。编译器会抱怨您正在返回一个引用,而您承诺该方法是 const 的,因此它将在不应修改其执行对象的情况下进行。这段代码将允许发生非法操作:

Person p( "Baltasar" );
p.getName() = "José";

cout << p.getName() << endl;

这就是为什么const在返回类型中出现作为一种新的有吸引力的选项,它将解决这个问题。常量引用不允许修改其指向的对象,如下所示:

const std::string &getName() const
    { return name; }

现在它可以编译了,而之前的恶意代码则不能。 现在让我们回到我们的问题:
const int &getA() const;
int a() const;

第二个是“按值返回”,这意味着在返回时将复制整数(4字节)。第一个表示将返回对整数(4字节)的常量引用。可以看出,在这种情况下,使用“按引用返回”而不是“按值返回”没有性能优势。
一般来说,“按const引用返回”始终是安全的,它永远不会比“按值返回”更昂贵。

1
当引用对象较小时,通过引用返回将更加昂贵,因为解引用需要时间。 - flownt
1
这两种方式都是实现同样效果的等效方式:几乎是。如果您使用返回值来初始化一个引用(例如 int const& ri = ...;),返回引用意味着您有可能会出现悬空引用的风险,而返回值则不会。 - James Kanze
你的意思是,如果引用恰好与“int”大小相同,则所做的工作量是等效的,但这并没有考虑客户端跟随引用并读取值所需付出的努力... - Tony Delroy
感谢你们的评论。@tony,@flownt,在我看来,复制和引用一个小值的成本是可以相比的。@Kanze,获取悬空引用的唯一方式(根据其本质)是返回方法中的局部变量的引用,而这里我们处理的是一个属性。另一方面,如果你尝试返回对局部变量的引用,编译器会发出警告。 - Baltasarq

2
const int& a() const;

a() 返回一个对 int 的常量引用。末尾的 const 修饰符表示它不能改变被调用对象的状态。

int getA() const;

与上述描述相同,只不过返回类型为int,如果收集,则涉及返回变量的复制。

当说无法更改对象状态时,这是什么意思?

class foo
{
    int m_Var ;
    public:
        foo(int arg1) : m_Var(arg1){}
        void mutableMethod()
        {
            m_Var = 20 ;  // "this" has the variable m_Var and
                          //  by assigning it a value changes the state of
                          //  m_Var. Changing the state of it's member variable
                          //  is meant changing the state of object.
        }

        void nonMutableMethod() const
        {
             m_Var = 20 ;  //  This assignment is not allowed because of const 
                           //  modifier. The method is not allowed to change the
                           //  the state of object on which it is called ( this )
        }
};

此外,常量方法不能通过非常量引用返回成员变量。

a() 返回一个 int 的常量引用。我认为值得一提的是 mutable 成员,除了 mutable 成员 以外,它不能改变被调用对象的状态。 - Alok Save
@Als:还有const_cast哦;-) - Tony Delroy
@Tony:我正要编辑添加这个,但你发现得真好;)我认为提到这些对于完全理解“const”是值得的。 - Alok Save

2
关键区别在于:
- `getA()` 返回一个 `int` 数据值,调用者可以完全独立于程序的其他部分使用它。 - `a()` 返回对 `a()` 选择的某个 `int` 的引用: - `int x = a()` 在那时“采样”该 int 值,并且在逻辑上等同于 `int x = getA()` - `const int& x = a()` 保存了 `a()` 返回的变量的引用!
保存引用并不总是如你所期望或想要的那样。
- 好处:编译器聪明到足以复制该变量(如果它是临时的/文字的,例如 `const int& x = a()`,`const int& a() { return 3; }`)。 - 好处或坏处?(取决于应用中是否有意义):每次稍后读取 `x` 的值时,程序可能会(尝试)从 `a()` 内部返回的原始 `int` 变量重新读取它:如果该变量的值已经改变,则 `x` 的值也会改变。(“可能”是因为优化器可以避免这种情况,当值无论如何都相同时) - 难看的地方:如果该地址上的内存不再存储该变量(例如,它在已经被删除的 `new` 出的内存中),则尝试读取 `x` 的值可能会导致不可预测的值或崩溃应用程序(如果该内存地址不再可读)。
`a()` 和 `getA()` 都是类的成员函数;我们知道这一点,因为只有成员函数可以是 `const`,它在技术上指示它们不能更改非 `mutable` 数据成员而不去掉其 constness,但该限制背后的意图是它们不应该修改对象的调用者可观察值;可变数据通常用于缓存、调试跟踪等。

0
我们可以将返回的引用转换为指针,因此在理论上,它比返回副本(值)提供了更多信息(地址和值)。
并且可以使用const_cast将const引用黑客变为可变。
编译器将尝试从原始寄存器、地址或文字中使用值。
达到什么目的的替代方法? 确保始终正确不会增加额外的工作量。 对于const引用和const,我发现CR&CN宏非常方便。
#define CN  const
#define CR  const&         // Constant reference
#define CDa const*         // mutable pointer to constant data
#define CPD const * const  // constant pointer to constant data
const int&  verbose() const;
int CR      shorter() CN;

副作用是声明变得更短,随着行数的减少,代码行数也会减少。这只是个人口味问题...但与DMAP宏结合使用时,似乎有优势。

typedef std::map<size_t, float>     TypeMap_Of_size_t_vs_float;
TypeMap_Of_size_t_vs_float          m_Map;
const TypeMap_Of_size_t_vs_float&   verboseIsNice() const
{
    return m_MyMap;
}
for each (auto myElement in verboseIsNice()) 
{
    myElement.foo();
}

对比

DMAP(SZ, Flo)  m_Map;                    // typedefs MSZFlo=std::map<size_t, float>
MSZFlo CR      tldr()                 CN { return m_Map; }
fe(el, tldr()) el.foo();

如果不使用自动化和使用迭代器,示例将显示超过333%的差异。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接