Kev*_*vin 228 c++ const class code-duplication c++-faq
假设class X我想要返回内部成员的访问权限:
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
Z& Z(size_t index)
{
// massive amounts of code for validating index
Z& ret = vecZ[index];
// even more code for determining that the Z instance
// at index is *exactly* the right sort of Z (a process
// which involves calculating leap years in which
// religious holidays fall on Tuesdays for
// the next thousand years or so)
return ret;
}
const Z& Z(size_t index) const
{
// identical to non-const X::Z(), except printed in
// a lighter shade of gray since
// we're running low on toner by this point
}
};
Run Code Online (Sandbox Code Playgroud)
的两个成员函数X::Z()和X::Z() const具有大括号内相同的代码.这是重复的代码,并且可能导致具有复杂逻辑的长函数的维护问题.
有没有办法避免这种代码重复?
jwf*_*arn 176
有关详细说明,请参见第28页码"避免复制const和非const成员函数".参见图23,在第3项" const尽可能使用",有效C++,3D编辑,Scott Meyers,ISBN-13:9780321334879.

这是迈耶斯的解决方案(简化):
struct C {
const char & get() const {
return c;
}
char & get() {
return const_cast<char &>(static_cast<const C &>(*this).get());
}
char c;
};
Run Code Online (Sandbox Code Playgroud)
两个强制转换和函数调用可能很丑,但它是正确的.迈耶斯有一个彻底的解释原因.
Kev*_*vin 59
是的,可以避免代码重复.您需要使用const成员函数来获取逻辑并让非const成员函数调用const成员函数并将返回值重新转换为非const引用(或者如果函数返回指针则返回指针):
class X
{
std::vector<Z> vecZ;
public:
const Z& Z(size_t index) const
{
// same really-really-really long access
// and checking code as in OP
// ...
return vecZ[index];
}
Z& Z(size_t index)
{
// One line. One ugly, ugly line - but just one line!
return const_cast<Z&>( static_cast<const X&>(*this).Z(index) );
}
#if 0 // A slightly less-ugly version
Z& Z(size_t index)
{
// Two lines -- one cast. This is slightly less ugly but takes an extra line.
const X& constMe = *this;
return const_cast<Z&>( constMe.Z(index) );
}
#endif
};
Run Code Online (Sandbox Code Playgroud)
注意:重要的是不要将逻辑放在非const函数中并让const函数调用非const函数 - 它可能导致未定义的行为.原因是常量类实例被转换为非常量实例.非const成员函数可能会意外地修改类,C++标准声明这将导致未定义的行为.
Dav*_*one 36
由于显式对象参数, C++23 已经更新了这个问题的最佳答案。
struct s {
auto && f(this auto && self) {
// all the common code goes here
}
};
Run Code Online (Sandbox Code Playgroud)
单个函数模板可作为普通成员函数进行调用,并为您推导出正确的引用类型。不会出现错误的转换,也不会为概念上是一件事的东西编写多个函数。
注意:此功能是由P0847 添加的:推导此。
Pai*_*ait 32
我认为Scott Meyers的解决方案可以通过使用tempate helper函数在C++ 11中得到改进.这使得意图更加明显,可以重复用于许多其他的getter.
template <typename T>
struct NonConst {typedef T type;};
template <typename T>
struct NonConst<T const> {typedef T type;}; //by value
template <typename T>
struct NonConst<T const&> {typedef T& type;}; //by reference
template <typename T>
struct NonConst<T const*> {typedef T* type;}; //by pointer
template <typename T>
struct NonConst<T const&&> {typedef T&& type;}; //by rvalue-reference
template<typename TConstReturn, class TObj, typename... TArgs>
typename NonConst<TConstReturn>::type likeConstVersion(
TObj const* obj,
TConstReturn (TObj::* memFun)(TArgs...) const,
TArgs&&... args) {
return const_cast<typename NonConst<TConstReturn>::type>(
(obj->*memFun)(std::forward<TArgs>(args)...));
}
Run Code Online (Sandbox Code Playgroud)
可以通过以下方式使用此辅助函数.
struct T {
int arr[100];
int const& getElement(size_t i) const{
return arr[i];
}
int& getElement(size_t i) {
return likeConstVersion(this, &T::getElement, i);
}
};
Run Code Online (Sandbox Code Playgroud)
第一个参数始终是this-pointer.第二个是指向要调用的成员函数的指针.之后,可以传递任意数量的附加参数,以便将它们转发给函数.由于可变参数模板,这需要C++ 11.
Dav*_*one 29
C++ 17已经更新了这个问题的最佳答案:
T const & f() const {
return something_complicated();
}
T & f() {
return const_cast<T &>(std::as_const(*this).f());
}
Run Code Online (Sandbox Code Playgroud)
这有以下优点:
volatile意外抛弃,但是volatile难得一见)如果你想要完整的扣除路线,那么可以通过辅助功能来完成
template<typename T>
constexpr T & as_mutable(T const & value) noexcept {
return const_cast<T &>(value);
}
template<typename T>
constexpr T * as_mutable(T const * value) noexcept {
return const_cast<T *>(value);
}
template<typename T>
constexpr T * as_mutable(T * value) noexcept {
return value;
}
template<typename T>
void as_mutable(T const &&) = delete;
Run Code Online (Sandbox Code Playgroud)
现在你甚至不能搞砸了volatile,用法看起来像
decltype(auto) f() const {
return something_complicated();
}
decltype(auto) f() {
return as_mutable(std::as_const(*this).f());
}
Run Code Online (Sandbox Code Playgroud)
Ste*_*sop 22
比迈耶斯更冗长,但我可能会这样做:
class X {
private:
// This method MUST NOT be called except from boilerplate accessors.
Z &_getZ(size_t index) const {
return something;
}
// boilerplate accessors
public:
Z &getZ(size_t index) { return _getZ(index); }
const Z &getZ(size_t index) const { return _getZ(index); }
};
Run Code Online (Sandbox Code Playgroud)
私有方法具有不受欢迎的属性,它返回一个非const Z&for const实例,这就是它为私有的原因.私有方法可能会破坏外部接口的不变量(在这种情况下,所需的不变量是"const对象不能通过它获得的引用修改为它具有的对象").
请注意,注释是模式的一部分 - _getZ的接口指定调用它永远无效(显然除了访问器):无论如何这样做都没有可能的好处,因为它输入了1个字符,而不是导致更小或更快的代码.调用该方法相当于使用const_cast调用其中一个访问器,您也不希望这样做.如果你担心明显错误(这是一个公平的目标),那么称之为const_cast_getZ而不是_getZ.
顺便说一句,我很欣赏迈耶斯的解决方案.我对它没有任何哲学上的反对意见.但就个人而言,我更喜欢一点点的受控重复,以及一种只能在某些严格控制的情况下调用的私有方法,而不是一种看起来像线路噪声的方法.选择你的毒药并坚持下去.
[编辑:Kevin正确地指出_getZ可能想要调用另一个方法(比如generateZ),这个方法与getZ一样是const专用的.在这种情况下,_getZ会看到一个const Z&并且必须在返回之前进行const_cast.这仍然是安全的,因为样板访问器可以控制所有内容,但是它的安全性并不明显.此外,如果你这样做,然后将generateZ更改为始终返回const,那么你还需要将getZ更改为始终返回const,但编译器不会告诉您这样做.
关于编译器的后一点也适用于Meyers的推荐模式,但关于非显而易见的const_cast的第一点不是.所以总的来说,我认为如果_getZ结果需要一个const_cast作为其返回值,那么这种模式会失去很多超过Meyers的价值.由于与Meyers相比它也有缺点,我想我会在那种情况下转向他.从一个重构到另一个很容易 - 它不会影响类中的任何其他有效代码,因为只有无效代码和样板文件调用_getZ.
gd1*_*gd1 18
好问题和好答案.我有另一种解决方案,它不使用强制转换:
class X {
private:
std::vector<Z> v;
template<typename InstanceType>
static auto get(InstanceType& instance, std::size_t i) -> decltype(instance.get(i)) {
// massive amounts of code for validating index
// the instance variable has to be used to access class members
return instance.v[i];
}
public:
const Z& get(std::size_t i) const {
return get(*this, i);
}
Z& get(std::size_t i) {
return get(*this, i);
}
};
Run Code Online (Sandbox Code Playgroud)
但是,它具有需要静态成员的丑陋以及在其中使用instance变量的需要.
我没有考虑这个解决方案的所有可能(负面)含义.如果有的话请告诉我.
您也可以使用模板解决此问题.这个解决方案稍微丑陋(但丑陋隐藏在.cpp文件中)但它确实提供了constness的编译器检查,并且没有代码重复.
.h文件:
#include <vector>
class Z
{
// details
};
class X
{
std::vector<Z> vecZ;
public:
const std::vector<Z>& GetVector() const { return vecZ; }
std::vector<Z>& GetVector() { return vecZ; }
Z& GetZ( size_t index );
const Z& GetZ( size_t index ) const;
};
Run Code Online (Sandbox Code Playgroud)
.cpp文件:
#include "constnonconst.h"
template< class ParentPtr, class Child >
Child& GetZImpl( ParentPtr parent, size_t index )
{
// ... massive amounts of code ...
// Note you may only use methods of X here that are
// available in both const and non-const varieties.
Child& ret = parent->GetVector()[index];
// ... even more code ...
return ret;
}
Z& X::GetZ( size_t index )
{
return GetZImpl< X*, Z >( this, index );
}
const Z& X::GetZ( size_t index ) const
{
return GetZImpl< const X*, const Z >( this, index );
}
Run Code Online (Sandbox Code Playgroud)
我可以看到的主要缺点是,因为该方法的所有复杂实现都在一个全局函数中,您需要使用上面的GetVector()等公共方法来获取X的成员(其中总是需要一个const和非const版本)或者你可以使这个功能成为朋友.但我不喜欢朋友.
[编辑:删除了在测试期间添加的cstdio不需要的包含.]
如果您不喜欢const转换,我可以使用另一个答案建议的模板静态辅助函数的 C++17 版本,以及可选的 SFINAE 测试。
#include <type_traits>
#define REQUIRES(...) class = std::enable_if_t<(__VA_ARGS__)>
#define REQUIRES_CV_OF(A,B) REQUIRES( std::is_same_v< std::remove_cv_t< A >, B > )
class Foobar {
private:
int something;
template<class FOOBAR, REQUIRES_CV_OF(FOOBAR, Foobar)>
static auto& _getSomething(FOOBAR& self, int index) {
// big, non-trivial chunk of code...
return self.something;
}
public:
auto& getSomething(int index) { return _getSomething(*this, index); }
auto& getSomething(int index) const { return _getSomething(*this, index); }
};
Run Code Online (Sandbox Code Playgroud)
完整版: https: //godbolt.org/z/mMK4r3
对于那些(像我一样)
这是另一种看法:
#include <utility>
#include <type_traits>
template <typename T> struct NonConst;
template <typename T> struct NonConst<T const&> {using type = T&;};
template <typename T> struct NonConst<T const*> {using type = T*;};
#define NON_CONST(func) \
template <typename... T> auto func(T&&... a) \
-> typename NonConst<decltype(func(std::forward<T>(a)...))>::type \
{ \
return const_cast<decltype(func(std::forward<T>(a)...))>( \
std::as_const(*this).func(std::forward<T>(a)...)); \
}
Run Code Online (Sandbox Code Playgroud)
它基本上是来自@Pait、@DavidStone 和@sh1 的答案的混合(编辑:以及@cdhowie 的改进)。它添加到表中的是您只需要一行额外的代码就可以简单地命名函数(但没有参数或返回类型重复):
class X
{
const Z& get(size_t index) const { ... }
NON_CONST(get)
};
Run Code Online (Sandbox Code Playgroud)
注意:gcc 无法在 8.1 之前编译它,clang-5 及更高版本以及 MSVC-19 很高兴(根据编译器资源管理器)。
虽然这里的大多数答案建议使用 a const_cast,但 CppCoreGuidelines 有一个关于此的部分:
相反,更喜欢共享实现。通常,您可以让非常量函数调用 const 函数。然而,当存在复杂的逻辑时,这可能会导致以下仍然诉诸 const_cast 的模式:
class Foo {
public:
// not great, non-const calls const version but resorts to const_cast
Bar& get_bar()
{
return const_cast<Bar&>(static_cast<const Foo&>(*this).get_bar());
}
const Bar& get_bar() const
{
/* the complex logic around getting a const reference to my_bar */
}
private:
Bar my_bar;
};
Run Code Online (Sandbox Code Playgroud)
虽然这种模式在正确应用时是安全的,但因为调用者必须有一个非常量对象开始,但它并不理想,因为安全性很难作为检查器规则自动强制执行。
相反,更喜欢将公共代码放在公共辅助函数中——并将其设为模板,以便它推导 const。这根本不使用任何 const_cast:
class Foo {
public: // good
Bar& get_bar() { return get_bar_impl(*this); }
const Bar& get_bar() const { return get_bar_impl(*this); }
private:
Bar my_bar;
template<class T> // good, deduces whether T is const or non-const
static auto& get_bar_impl(T& t)
{ /* the complex logic around getting a possibly-const reference to my_bar */ }
};
Run Code Online (Sandbox Code Playgroud)
注意:不要在模板内进行大量的非依赖工作,这会导致代码膨胀。例如,如果 get_bar_impl 的全部或部分可以是非依赖的并分解为公共非模板函数,则进一步的改进将可能大大减少代码大小。
| 归档时间: |
|
| 查看次数: |
30371 次 |
| 最近记录: |