在const和non-const functions间复用代码

本文探讨了C++中如何避免const和non-const getter方法的代码重复,并针对成员函数const与non-const之间的冲突提供了四种方案:代码复制、函数模板、const_cast使用和mutable成员变量。作者分析了各种方法的优缺点,并按优劣排序给出建议。

const getter和non-const getter

在C++中,我们经常会用到const版本的getter和non-const的getter, const 版本不会修改成员对象,非const 版本用来进行修改,如下面来自Stackoverflow类似的代码:

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
    }
};

X::Z()和X::Z() const 内部拥有相同的代码逻辑,怎么复用代码避免重复呢?

Scott Meyers 在<< Effective C++>>的"Avoid Duplication in const and Non-const Member Function" 中给出了一个简单的方案:

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) );
   }
}

const function和non-const function

那么,遇到下面的问题怎么解决呢,示例代码来自cppstories:

struct Part {
    std::string _name;
    bool _isAvailable { false };
};

class PartsStore {
public:
    PartsStore(const std::map<int, Part>& parts) : _parts(parts) { }
    
    bool Contains(std::string_view name) {
        return FindByNameImpl(name) != nullptr;
    }
    
    void SetAvailability(std::string_view name, bool isAvailable) {
        auto pPart = FindByNameImpl(name);
        if (pPart)
            pPart->_isAvailable = isAvailable;
    }
    
private:    
    Part* FindByNameImpl(std::string_view name) {
        auto it = std::find_if(begin(_parts), end(_parts), 
            [&name](const auto& entry) {
                return entry.second._name == name;
            }
        );
        if (it != _parts.end())
            return &it->second;
        return nullptr;
    }
    
    std::map<int, Part> _parts;    
};

上面的代码有个问题:

  • PartsStore::Contains()方法只需要判断某个Part是否存在,需要设置为const,那么PartsStore::FindByNameImpl()就必须为const

但是,PartsStore::SetAvailability()需要修改某个Part的属性,需要保持为non-const, 那么PartsStore::FindByNameImpl()就必须为non-const,这是互相冲突的,怎么解决这个问题呢?

方案1

提供const和non-const版本的PartsStore::FindByNameImpl(),类似第一部分的做法:non-const版本的调用const版本的方法,避免code重复,具体代码如下,也可以直接在@wandbox这里运行:

#include <iostream>
#include <map>
#include <stack>
#include <string>
#include <vector>
#include <string_view>

struct Part {
    std::string _name;
    bool _isAvailable { false };
};

class PartsStore {
    
public:
    PartsStore(const std::map<int, Part>& parts) : _parts(parts) { }
    
    bool Contains(std::string_view name) const {
        return FindByNameImpl(name) != nullptr;
    }
    
    void SetAvailability(std::string_view name, bool isAvailable) {
        auto pPart = FindByNameImpl(name);
        if (pPart)
            pPart->_isAvailable = isAvailable;
    }
    
private:    
    const Part* FindByNameImpl(std::string_view name) const {
        auto it = std::find_if(begin(_parts), end(_parts), [&name](const auto& entry) {
            return entry.second._name == name;
        });
        if (it != _parts.end())
            return &it->second;
        return nullptr;
    }
    
    Part* FindByNameImpl(std::string_view name) {
        return const_cast<Part*>(static_cast<const PartsStore&>(*this).FindByNameImpl(name));
    }
    
    std::map<int, Part> _parts;    
};

int main() {
	PartsStore store( { 
        { 1, { "box" } },
        { 2, { "tv" } },
    }); 
    
    std::cout << store.Contains("box") << '\n';
    std::cout << store.Contains("apple") << '\n';
}

方案2

将std::map<int, Part> _parts改为mutable,关于mutable,《C++ Core Guidelines》: ES 50 中有比较清晰的说明,mutable适用于一些属于中间状态的成员

Sometimes, “cast away const” is to allow the updating of some transient information of an otherwise immutable object. Examples are caching, memoization, and precomputation. Such examples are often handled as well or better using mutable or an indirection than with a const_cast.

显然,_parts这里不属于中间状态,这个方案是一个不好的方案。

方案3

利用const_cast来转换:

class PartsStore {
    
public:
    PartsStore(const std::map<int, Part>& parts) : _parts(parts) { }
    
    bool Contains(std::string_view name) const {
        return FindByNameImpl(name) != nullptr;
    }
    
    void SetAvailability(std::string_view name, bool isAvailable) {
        auto pPart = const_cast<Part*>(FindByNameImpl(name));
        if (pPart)
            pPart->_isAvailable = isAvailable;
    }
    
private:       
    const Part* FindByNameImpl(std::string_view name) const {
        // impl...
    }
    
    std::map<int, Part> _parts;    
};

这样,我们cast掉了返回值const Part*前的const, 同时,SetAvailability是一个non-const function,可以调用PartsStore::FindByNameImpl() const版本的函数,这个方案看起来不错,但是也不是完美的,由于cast掉了const了,我们通过一个const型的function修改了对象内部成员的值,违背了function const的承诺。

方案4

采用函数模板的方法,给模板多加一个参数:_parts成员变量类型。通过function本身的cv修饰符,function内部的_parts成员具有一致的cv修饰符,这样,编译器就会生成两个不同版本的模板函数:

template <typename T>
static auto FindByNameImpl(std::string_view name, T& container) {
    auto it = std::find_if(begin(container), end(container), 
        [&name](const auto& entry) {
             return entry.second._name == name;
        }
    );

    return it != end(container) ? &it->second : nullptr;
}

完整代码可以参见这里@wandbox

方案从好到坏排序为:方案1 > 方案4 > 方案3 > 方案2


Reference:

  1. https://riptutorial.com/cplusplus/example/16974/avoiding-duplication-of-code-in-const-and-non-const-getter-methods-
  2. https://stackoverflow.com/questions/123758/how-do-i-remove-code-duplication-between-similar-const-and-non-const-member-func/123995
#ifndef _AP4_UTILS_H_ #define _AP4_UTILS_H_ /*---------------------------------------------------------------------- | includes +---------------------------------------------------------------------*/ #include "Ap4Config.h" #include "Ap4Types.h" #include "Ap4Results.h" #include "Ap4Config.h" #include "Ap4List.h" #include "Ap4String.h" #include "Ap4DataBuffer.h" /*---------------------------------------------------------------------- | global options +---------------------------------------------------------------------*/ class AP4_GlobalOptions { public: static bool GetBool(const char* name); static void SetBool(const char* name, bool value); static const char* GetString(const char* name); static void SetString(const char* name, const char* value); private: struct Entry { AP4_String m_Name; AP4_String m_Value; }; static Entry* GetEntry(const char* name, bool autocreate); static AP4_List<Entry>* g_Entries; }; /*---------------------------------------------------------------------- | non-inline functions +---------------------------------------------------------------------*/ double AP4_BytesToDoubleBE(const unsigned char* bytes); AP4_UI64 AP4_BytesToUInt64BE(const unsigned char* bytes); void AP4_BytesFromDoubleBE(unsigned char* bytes, double value); void AP4_BytesFromUInt64BE(unsigned char* bytes, AP4_UI64 value); /*---------------------------------------------------------------------- | AP4_BytesToUInt32BE +---------------------------------------------------------------------*/ inline AP4_UI32 AP4_BytesToUInt32BE(const unsigned char* bytes) { return ( ((AP4_UI32)bytes[0])<<24 ) | ( ((AP4_UI32)bytes[1])<<16 ) | ( ((AP4_UI32)bytes[2])<<8 ) | ( ((AP4_UI32)bytes[3]) ); } /*---------------------------------------------------------------------- | AP4_BytesToInt32BE +---------------------------------------------------------------------*/ inline AP4_SI32 AP4_BytesToInt32BE(const unsigned char* bytes) { return AP4_BytesToUInt32BE(bytes); } /*---------------------------------------------------------------------- | AP4_BytesToUInt24BE +---------------------------------------------------------------------*/ inline AP4_UI32 AP4_BytesToUInt24BE(const unsigned char* bytes) { return ( ((AP4_UI32)bytes[0])<<16 ) | ( ((AP4_UI32)bytes[1])<<8 ) | ( ((AP4_UI32)bytes[2]) ); } /*---------------------------------------------------------------------- | AP4_BytesToInt16BE +---------------------------------------------------------------------*/ inline AP4_UI16 AP4_BytesToUInt16BE(const unsigned char* bytes) { return ( ((AP4_UI16)bytes[0])<<8 ) | ( ((AP4_UI16)bytes[1]) ); } /*---------------------------------------------------------------------- | AP4_BytesToInt16BE +---------------------------------------------------------------------*/ inline AP4_SI16 AP4_BytesToInt16BE(const unsigned char* bytes) { return (AP4_SI16)AP4_BytesToUInt16BE(bytes); } /*---------------------------------------------------------------------- | AP4_BytesFromUInt32BE +---------------------------------------------------------------------*/ inline void AP4_BytesFromUInt32BE(unsigned char* bytes, AP4_UI32 value) { bytes[0] = (unsigned char)((value >> 24)&0xFF); bytes[1] = (unsigned char)((value >> 16)&0xFF); bytes[2] = (unsigned char)((value >> 8)&0xFF); bytes[3] = (unsigned char)((value )&0xFF); } /*---------------------------------------------------------------------- | AP4_BytesFromUInt24BE +---------------------------------------------------------------------*/ inline void AP4_BytesFromUInt24BE(unsigned char* bytes, AP4_UI32 value) { bytes[0] = (unsigned char)((value >> 16)&0xFF); bytes[1] = (unsigned char)((value >> 8)&0xFF); bytes[2] = (unsigned char)((value )&0xFF); } /*---------------------------------------------------------------------- | AP4_BytesFromUInt16BE +---------------------------------------------------------------------*/ inline void AP4_BytesFromUInt16BE(unsigned char* bytes, AP4_UI16 value) { bytes[0] = (unsigned char)((value >> 8)&0xFF); bytes[1] = (unsigned char)((value )&0xFF); } 详细注释上述代码
最新发布
09-26
<think> 首先,用户要求提供关于"non-const lvalue reference to type"的信息。这涉及到C++中的左值引用、右值引用值类别。 从提供的引用中: - 引用[1]讨论了错误信息,指出非const引用不能绑定到右值的问题。 - 引用[2]澄清了即使有const,也不能成为万能引用(universal reference)。 - 引用[3]描述了值传递引用传递在拷贝构造函数调用上的区别。 用户的核心问题是:"non-const lvalue reference to type",所以我需要解释什么是non-const lvalue reference,它的限制,以及与右值的关系。 关键点来自于引用[1]:non-const引用不能绑定到右值,只能绑定到左值。引用[2]强调了const T&&是右值引用,不是万能引用。引用[3]展示了常量左值引用可以绑定到右值。 系统级指令: - 所有行内数学表达式用$...$,但这里没有数学表达式,主要是代码概念。 - 独立公式用$$...$$,但我可能不需要公式。 - LaTeX语法正确:在回答中,如果有需要,我会用LaTeX格式。 - 使用中文回答:整个响应必须是中文。 - 生成相关问题:在回答结束后,添加相关问题。 - 引用标识:在引用的段落末尾添加[^1], [^2]等,但指令说"回答中引用的段落末尾自然地添加引用标识"。既然用户提供了引用,我应该在使用这些引用时添加标识。 用户可见层: - 回答结构清晰,步骤合理:我需要组织内容,帮助用户理解non-const lvalue reference。 - 尽量保证真实可靠:基于标准C++知识。 结构化回答: 1. **解释什么是non-const lvalue reference**:在C++中,引用是一种别名,左值引用绑定到左值。 2. **限制**:non-const左值引用只能绑定到可修改的左值,不能绑定到右值或常量左值。 3. **错误示例**:从引用[1]中,错误信息invalid initialization of non-const reference。 4. **对比**:与const左值引用右值引用比较。 5. **代码示例**:展示正确的错误的代码。 6. **总结**:何时使用避免此类引用。 相关引用: - 引用[1]中提到左值(lvalue)右值(rvalue)的概念。 - 引用[2]区分右值引用万能引用。 - 引用[3]演示常量左值引用绑定到右值。 在回答中,自然地引用这些: - 例如,当解释为什么non-const引用不能绑定到右值时,引用[^1]。 - 当讨论const引用时,引用[^3]。 相关问题: - 系统指令要求生成相关问题,放在最后,以"§§相关问题§§"开头。 - 问题应该基于主题:如左值、右值、引用类型等。 完整响应结构: - 开头:直接回答问题。 - 主体:逐步解释。 - 结尾:相关问题。 </think> ### 非const左值引用到类型的信息 在C++编程语言中,"non-const lvalue reference to type"(非const左值引用到一个类型)是一个常见但容易引发错误的引用类型。它允许你创建对一个对象的别名,该对象必须是可修改的左值(lvalue)。我将基于标准C++规范(C++11及更高版本),参考您提供的引用内容,逐步解释其核心概念、限制、典型错误应用场景。回答力求清晰可靠。 #### 1. **基本概念** - **什么是左值引用(lvalue reference)**:左值引用(例如 `int& ref`)绑定到一个左值对象上,该对象必须有明确的存储位置(如变量),允许通过引用修改对象的值。左值(lvalue)指表达式结束后依然存在的对象,如变量名或数组元素。 - **非const左值引用**:这是一种没有 `const` 修饰符的左值引用(如 `int& ref`),它只能绑定到**可修改的左值**上。这意味着引用对象必须是可读写的,例如: ```cpp int x = 10; // x 是一个左值 int& ref = x; // 正确:非const左值引用绑定到左值x ref = 20; // 可以修改x的值 ``` - **关键限制**:非const左值引用不能绑定到右值(rvalue),因为右值代表临时对象(如临时计算结果),没有持久存储位置。试图这样做会引发编译错误,正如引用[1]中提到的错误信息:"invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int'" [^1]。 #### 2. **为什么非const左值引用不能绑定到右值?** - **左值 vs. 右值**:在C/C++中,左值(lvalue)指有存储地址的对象(如变量),而右值(rvalue)指临时的、即将销毁的对象(如字面量或函数返回的临时值)。例如: - `int y = 5;` // `5` 是右值,`y` 是左值 - `int& ref = 5;` // 错误:5 是右值,不能绑定到非const引用 - **错误原因**:非const左值引用需要修改对象,但右值是临时的,无法安全地修改(可能导致未定义行为)。编译器强制执行此规则以防止逻辑错误,如引用[1]所述,错误信息提示了左值右值的冲突 [^1]。 - **代码示例**: ```cpp int foo() { return 42; } // 函数返回右值(临时int) int main() { int& ref1 = foo(); // 错误:非const引用不能绑定到右值(编译失败) const int& ref2 = foo(); // 正确:const左值引用可以绑定到右值 return 0; } ``` 这里,`ref1` 的初始化会触发引用[1]中的错误信息,而 `ref2` 成功是因为常量左值引用(`const int&`)可以延长右值的生命周期 [^3]。 #### 3. **与非const引用的对比与陷阱** - **与const左值引用的区别**:常量左值引用(例如 `const int&`)可以绑定到左值或右值,因为它承诺不修改对象,提供更高的灵活性。引用[3]展示了这个特性:常量左值引用在函数传参时避免不必要的拷贝,提高性能 [^3]。 - **与右值引用的区别**:右值引用(例如 `int&&`)专门绑定到右值,支持移动语义(移动构造函数),但非const左值引用不能用于右值。引用[2]强调,即使添加 `const` 修饰符(如 `const T&&`),它仍然是右值引用,不是万能引用(universal reference) [^2]。 - **常见陷阱**: - **函数返回问题**:如果函数返回局部变量的左值引用,会导致悬垂引用(dangling reference),因为对象生命周期结束。非const引用增加了风险。 - **编译错误**:在模板或泛型代码中,误用非const左值引用绑定右值是常见错误源,如引用[1]的示例 [^1]。 #### 4. **最佳实践应用场景** - **何时使用**:非const左值引用适用于需要直接修改外部对象的场景,例如: - 函数参数传递:`void modify(int& val) { val *= 2; }` // 直接修改传入变量。 - 避免拷贝:当对象较大时(如自定义类),使用引用提高效率。 - **何时避免**: - 如果需要绑定临时对象,改用const左值引用或右值引用。 - 在C++11+中,优先使用移动语义(`std::move`)处理右值,减少拷贝开销。 - **性能考虑**:非const左值引用本身不增加运行时开销(仅编译时别名),但错误使用会导致编译失败或未定义行为。 总结来说,非const左值引用是一个强大但受限制的工具,它要求绑定对象必须是可修改的左值。正确地使用它可以提升代码效率,但忽视其限制(特别是与右值的冲突)会引发编译错误或逻辑问题。参考引用[1]、[2][3]的内容,这些概念在C++内存管理值类别中至关重要 [^1][^2][^3]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值