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

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值