委派模板成员功能的良好设计

Good design for delegating template-member functionality

本文关键字:功能 成员 委派      更新时间:2023-10-16

我很难为以下场景找到简单而优雅的设计。类 Worker 使用模板类Helper来执行一些工作。在简单场景中,它看起来像这样:

template<typename T>
class Helper {
  public:
    void Help(T data) : m_data(data) {} //NOTICE: Copy
    T const& Data() const { return m_data; }
    T m_data;
}
class SimpleWorker {
  public:
    SimpleWorker() : m_helper(SimpleData()) {} // non-temp used in reality
    void DoWork()
    {
        m_helper.help();
    }        
    Helper<SimpleData> m_helper;
}

当模板参数更复杂并且与工作人员属于同一业务领域时,事情对我来说变得复杂。工作线程需要使用帮助程序,但它还需要在数据对象上调用一些帮助程序甚至不知道的方法(在此设计中)。像这样:

template<typename T>
class Helper {
  public:
    Helper(T data) : m_data(data) {} //NOTICE: Copy
    T const& Data() const { return m_data; }
    T m_data;
}
class ComplexWorker {
  public:
    ComplexWorker() : m_helper(ComplexData()) {} // non-temp used in reality
    void DoWork()
    {
        m_helper.help();
        m_helper.GetData().DoSomethingComplexNotConst(); // <-------------
    }    
    Helper<ComplexData> m_helper;    
}

明显的问题是我不能在结果上调用 not const 函数Data()。使Data() non-const 似乎是一个坏主意,因为Helper也用于不同的上下文。我的目标是找到一种优雅的方式来使用其成员函数更改ComplexData ComplexWorker首先需要更改ComplexData,以便Helper可以继续处理更改后的数据。

编辑:更改Helper构造以复制提供的数据以更好地类似于实际流程

我认为最好让Helper只有静态函数,而不是维护状态(因为您在自己的代码中创建临时ComplexData() ComplexWorker)。通过引用或常量引用传递数据,具体取决于是否需要修改。

// primary template
template<typename T>
class Helper {
public:
    static void help(T const& data) const {} // non-modifying
};
// specialization for ComplexData
template<>
class Helper<ComplexData> {
public:
    static void help(ComplexData const& data) const { } // non-modifying
    static void DoSomethingComplexNotConst(ComplexData& data) // modifying
    {
         // your implementation here
    }
};
class ComplexWorker {
public: 
    ComplexWorker() : m_data(ComplexData()) {} // create new data
    void DoWork()
    {
        Helper<ComplexData>::help(m_data);
        Helper<ComplexData>::DoSomethingComplexNotConst(m_data); // <--- now no problem
    }
   private:
       ComplexData m_data;         
};

请注意,我为ComplexData做了一个模板专用化。help()中存在一些代码重复,但您可以轻松地将其提取到一个通用的非成员帮助程序函数中。

在我看来

,这取决于Helper实际在做什么。您的示例仅给出了一个构造函数和一个访问器,但我怀疑这就是它在实践中所做的一切。

您是否考虑过简单地使用继承?然后,您的Helper模板将如下所示:

template<typename T>
class Helper : public T {
    Helper(T data) : T(data) {}
    void Help() {};
}

在这种情况下,您可以直接在"is-a"关系中使用Helper<ComplexData>对象:

class ComplexWorker {
    Helper<ComplexData> m_helper;
    void DoWork()
    {
        m_helper.help();
        m_helper.DoSomethingComplexNotConst();
    }        
}

为什么不参考 STL 中容器部分的实现。Data() 函数的重载可以在安全和优雅之间取得平衡。

template <typename T>
class Helper {
public:
    Helper(T data) : m_data(data) {} //NOTICE: Copy
    T const& Data() const { return m_data; }
    T& Data() {return m_data; }
private:
    T m_data;
}
class ComplexWorker {
public:
    ComplexWorker() : m_helper(ComplexData()) {} // non-temp used in reality
    void DoWork()
    {
        m_helper.help();
        ComplexData &cd1 = m_helper.Data();
        cd1.QuerySth();
        const ComplexData &cd2 = m_helper.Data();
        cd2.ModifySth();
    }    
private:
    Helper<ComplexData> m_helper;    
}