C++模板来覆盖常量和非常量方法

C++ template to cover const and non-const method

本文关键字:常量 方法 非常 覆盖 C++      更新时间:2023-10-16

对于const和非const版本,我遇到了重复相同代码的问题。我可以用一些代码来说明这个问题。这里有两个示例访问者,一个修改被访问对象,另一个不修改。

struct VisitorRead 
{
    template <class T>
    void operator()(T &t) { std::cin >> t; }
};
struct VisitorWrite 
{
    template <class T> 
    void operator()(const T &t) { std::cout << t << "n"; }
};

现在这里有一个聚合对象——它只有两个数据成员,但我的实际代码要复杂得多:

struct Aggregate
{
    int i;
    double d;
    template <class Visitor>
    void operator()(Visitor &v)
    {
        v(i);
        v(d);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        v(i);
        v(d);
    }
};

和一个功能来证明以上:

static void test()
{
    Aggregate a;
    a(VisitorRead());
    const Aggregate b(a);
    b(VisitorWrite());
}

现在,这里的问题是const和非const版本的Aggregate::operator()的重复。

有没有可能以某种方式避免这种代码的重复?

我有一个解决方案:

template <class Visitor, class Struct>
void visit(Visitor &v, Struct &s) 
{
    v(s.i);
    v(s.i);
}
static void test2()
{
    Aggregate a;
    visit(VisitorRead(), a);
    const Aggregate b(a);
    visit(VisitorWrite(), b);
}

这意味着不需要Aggregate::operator(),也不存在重复。但我对visit()是泛型的而没有提到类型Aggregate这一事实感到不舒服。

有更好的方法吗?

我倾向于喜欢简单的解决方案,所以我会选择自由函数的方法,可能会添加SFINAE来禁用Aggregate:以外的类型的函数

template <typename Visitor, typename T>
typename std::enable_if< std::is_same<Aggregate,
                                   typename std::remove_const<T>::type 
                                  >::value
                       >::type
visit( Visitor & v, T & s ) {  // T can only be Aggregate or Aggregate const
    v(s.i);
    v(s.d);   
}

如果没有启用C++0x的编译器(或者可以从boost type_traits中借用它们(,那么enable_ifis_sameremove_const实际上很容易实现

EDIT:在编写SFINAE方法时,我意识到在OP中提供纯模板(无SFINAE(解决方案存在很多问题,其中包括如果您需要提供多个可访问类型,不同的模板会发生冲突(即,它们与其他模板一样匹配(。通过提供SFINAE,您实际上只为满足条件的类型提供visit函数,将奇怪的SFINAE转换为等效的:

// pseudocode, [] to mark *optional*
template <typename Visitor>
void visit( Visitor & v, Aggregate [const] & s ) {
   v( s.i );
   v( s.d );
}
struct Aggregate
{
    int i;
    double d;
    template <class Visitor>
    void operator()(Visitor &v)
    {
        visit(this, v);
    }
    template <class Visitor>
    void operator()(Visitor &v) const
    {
        visit(this, v);
    }
  private:
    template<typename ThisType, typename Visitor>
    static void visit(ThisType *self, Visitor &v) {
        v(self->i);
        v(self->d);
    }
};

好的,所以仍然有一些样板,但没有依赖于Aggregate的实际成员的代码重复。与Scott Meyers为避免getter中的重复而倡导的const_cast方法不同,编译器将确保两个公共函数的常量正确性。

由于您的最终实现并不总是相同的,因此我认为没有真正的解决方案来解决您所感知的"问题"。

让我们考虑一下。我们必须满足Aggregate是常量或非常量的情况。当然,我们不应该放松这一点(例如,只提供一个非常量版本(。

现在,运算符的常量版本只能调用通过常量ref(或值(获取参数的访问者,而非常量版本可以调用任何访问者。

您可能认为可以用另一个实现来替换这两个实现中的一个。要做到这一点,您将始终根据非const版本来实现const版本,而不是相反。假设:

void operator()(Visitor & v) { /* #1, real work */ }
void operator()(Visitor & v) const
{
  const_cast<Aggregate *>(this)->operator()(v);  // #2, delegate
}

但为了使这一点有意义,第2行要求操作在逻辑上是不可变的。例如,在典型的成员访问运算符中,可以提供对某个元素的常量或非常量引用。但在您的情况下,您不能保证operator()(v)调用在*this上不会发生变化!

因此,你的两个功能确实有很大的不同,尽管它们在形式上看起来很相似。你不能用另一个来表达一个。

也许你可以从另一个角度来看:你的两个功能实际上并不相同。在伪代码中,它们是:

void operator()(Visitor & v) {
  v( (Aggregate *)->i );
  v( (Aggregate *)->d );
}
void operator()(Visitor & v) const {
  v( (const Aggregate *)->i );
  v( (const Aggregate *)->d );
}

事实上,仔细想想,也许如果你愿意修改一下签名,可以做一些事情:

template <bool C = false>
void visit(Visitor & v)
{
  typedef typename std::conditional<C, const Aggregate *, Aggregate *>::type this_p;
  v(const_cast<this_p>(this)->i);
  v(const_cast<this_p>(this)->d);
}
void operator()(Visitor & v) { visit<>(v); }
void operator()(Visitor & v) const { const_cast<Aggregate *>(this)->visit<true>()(v); }

通常情况下,使用有意义的方法可能会更好。例如,load()save()。他们说了一些关于通过访客进行的操作的具体内容。通常同时提供常量和非常量版本(不管怎样,对于访问器之类的东西(,所以它看起来只是重复的,但可能会在以后的调试中省去一些麻烦。如果您真的想要一个变通方法(我不建议(,那就是声明方法const和所有成员mutable

添加访问者特征来判断它是否在修改(常量或非常量使用(。这是STL迭代器使用的。

您可以使用const_cast并更改VisitorRead的方法签名,这样它也可以使用const T&作为一个参数,但我认为这是一个丑陋的解决方案。

另一个解决方案-要求Visitor类具有一个在应用时添加const的元函数:

template <class Visitor>
static void visit(Visitor &v, typename Visitor::ApplyConst<Aggregate>::Type &a)
{
    v(a.i);
    v(a.d);
}