关于在C++中重载[]
运算符的另一个问题,特别是它的const
版本。
根据关于运算符重载的cppreference页面,当重载数组下标运算符时
struct T { value_t& operator[](std::size_t idx) { return mVector[idx]; } const value_t& operator[](std::size_t idx) const { return mVector[idx]; } };
如果已知值类型是内置类型,则const变量应按值返回。
因此,如果value_t
恰好是内置类型,则const
变体应该看起来是
const value_t operator[](std::size_t idx) const { return mVector[idx]; }
甚至可能是
value_t operator[](std::size_t idx) const { return mVector[idx]; }
因为CCD_ 5限定符对于这样的返回值不是很有用。
现在,我有一个模板化的class T
(保持与引用相同的命名),它被和内置数据类型以及用户定义的数据类型使用,其中一些可能很重。
template<class VT>
struct T
{
VT& operator[](std::size_t idx) { return mVector[idx]; }
const VT& operator[](std::size_t idx) const { return mVector[idx]; }
};
根据上面给出的建议,我应该使用enable_if
和一些type_traits
来区分具有内置/非内置类型的模板化类实例化。
我必须这么做吗?这项建议是否只是为了避免对内置类型潜在的不必要的取消引用,或者背后隐藏着其他应该注意的东西?
备注:
- 这个类积极参与代码的热点部分,该部分用内置类型和自定义类型实例化
- 代码跨平台使用,多个编译器具有不同程度的优化选项
- 因此,我有兴趣使它既正确又正确;便携式,并避免任何潜在的性能损害
- 我在C++标准中找不到任何额外的推理,但阅读standardeze并不是我最擅长的
StackOverflow上存在的问题:
关于运算符重载的基本规则和习惯用法的C++FAQ条目建议在没有明确理由的情况下使用"应该更好"构造返回副本。这个"应该更好"与cppreference页面中的"应该"略有不同,这让我感到困惑const value_t&
,第二个使用const value_t
我不同意上面的"建议"。考虑一下:
T t = /*Initialize `t`*/;
const T::value_t &vr = std::as_const(t)[0];
const auto test = vr; //Copy the value
t[0] = /*some value other than the original one.*/
assert(test != vr);
断言是否触发?它不应该触发,因为我们只是在引用容器中的值。基本上,std::as_const(t)[i]
应该具有与std::as_const(t[i])
相同的效果。但如果您的const
版本返回一个值,则不会。因此,进行这样的更改从根本上改变了代码的语义。
因此,即使您知道value_t
是一个基本类型,您仍然应该返回一个const&
。
注意,C++20范围正式识别不从其operator*
或等效函数返回实际value_type&
的范围。但即便如此,这些东西也是该范围本质的一个基本部分,而不是一个基于模板参数而变化的属性(请参阅vector<bool>
,了解为什么这是一个坏主意)。
您不需要以特殊的方式处理基本类型。对于非const
变体,总是简单地返回value_t&
,对于const
变体,总是返回const value_t&
。
重载通常很短,就像您的例子一样,所以它们无论如何都会在每个调用站点内联。在这种情况下,重载是通过值返回还是通过引用返回都无关紧要,在任何一种情况下,间接性都将被优化掉。任何设置为至少低优化级别的现代编译器都应该这样做。
我想不出有任何其他理由以不同的方式处理基本类型。
我还要提醒您,如果您像示例中那样实现容器类,则返回引用和返回值对用户来说将具有不同的语义。
如果返回对元素的const
引用,则用户可以保留该引用并观察容器元素中的更改(直到引用以某种指定方式失效为止)。如果返回一个值,则无法观察到变化。
如果用户能够获得某些类型的引用并观察到未来的变化,但不能观察到其他类型的变化,那将是令人惊讶的。最糟糕的情况是,如果他们也使用类型泛型代码,他们还需要对所有模板进行条件设置。
此外,即使对于基本类型按值返回,也只能通过对对象的const
引用来调用按值返回的重载。在大多数情况下,用户可能有一个容器的非const
实例,为了利用这种潜在的优化,他们需要在调用运算符重载之前先将对象引用显式转换为const
。
因此,如果优化是一个问题,我宁愿添加一个额外的成员函数,总是按值返回容器元素的副本,如果潜在的取消引用被确定为性能问题,则用户可以使用该函数。调用这个成员函数不会比确保调用正确的运算符重载更麻烦。