How do I remove code duplication between similar const and non-const member functions?

后端 未结 19 1668
天涯浪人
天涯浪人 2020-11-22 00:30

Let\'s say I have the following class X where I want to return access to an internal member:

class Z
{
    // details
};

class X
{
    std::vec         


        
19条回答
  •  被撕碎了的回忆
    2020-11-22 00:56

    It's surprising to me that there are so many different answers, yet almost all rely on heavy template magic. Templates are powerful, but sometimes macros beat them in conciseness. Maximum versatility is often achieved by combining both.

    I wrote a macro FROM_CONST_OVERLOAD() which can be placed in the non-const function to invoke the const function.

    Example usage:

    class MyClass
    {
    private:
        std::vector data = {"str", "x"};
    
    public:
        // Works for references
        const std::string& GetRef(std::size_t index) const
        {
            return data[index];
        }
    
        std::string& GetRef(std::size_t index)
        {
            return FROM_CONST_OVERLOAD( GetRef(index) );
        }
    
    
        // Works for pointers
        const std::string* GetPtr(std::size_t index) const
        {
            return &data[index];
        }
    
        std::string* GetPtr(std::size_t index)
        {
            return FROM_CONST_OVERLOAD( GetPtr(index) );
        }
    };
    

    Simple and reusable implementation:

    template 
    T& WithoutConst(const T& ref)
    {
        return const_cast(ref);
    }
    
    template 
    T* WithoutConst(const T* ptr)
    {
        return const_cast(ptr);
    }
    
    template 
    const T* WithConst(T* ptr)
    {
        return ptr;
    }
    
    #define FROM_CONST_OVERLOAD(FunctionCall) \
      WithoutConst(WithConst(this)->FunctionCall)
    

    Explanation:

    As posted in many answers, the typical pattern to avoid code duplication in a non-const member function is this:

    return const_cast( static_cast(this)->Method(args) );
    

    A lot of this boilerplate can be avoided using type inference. First, const_cast can be encapsulated in WithoutConst(), which infers the type of its argument and removes the const-qualifier. Second, a similar approach can be used in WithConst() to const-qualify the this pointer, which enables calling the const-overloaded method.

    The rest is a simple macro that prefixes the call with the correctly qualified this-> and removes const from the result. Since the expression used in the macro is almost always a simple function call with 1:1 forwarded arguments, drawbacks of macros such as multiple evaluation do not kick in. The ellipsis and __VA_ARGS__ could also be used, but should not be needed because commas (as argument separators) occur within parentheses.

    This approach has several benefits:

    • Minimal and natural syntax -- just wrap the call in FROM_CONST_OVERLOAD( )
    • No extra member function required
    • Compatible with C++98
    • Simple implementation, no template metaprogramming and zero dependencies
    • Extensible: other const relations can be added (like const_iterator, std::shared_ptr, etc.). For this, simply overload WithoutConst() for the corresponding types.

    Limitations: this solution is optimized for scenarios where the non-const overload is doing exactly the same as the const overload, so that arguments can be forwarded 1:1. If your logic differs and you are not calling the const version via this->Method(args), you may consider other approaches.

提交回复
热议问题