C ++模板覆盖const和非const方法

时间:2011-10-17 09:53:04

标签: c++ templates visitor

我遇到了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());
}

现在,问题在于Aggregate::operator()const版本的重复const

是否有可能避免重复此代码?

我有一个解决方案就是:

template <class Visitor, class Struct>
void visit(Visitor &v, Struct &s) 
{
    v(s.i);
    v(d.i);
}

static void test2()
{
    Aggregate a;
    visit(VisitorRead(), a);
    const Aggregate b(a);
    visit(VisitorWrite(), b);
}

这意味着不需要Aggregate::operator()且没有重复。但我对visit()是通用的而不提及类型Aggregate这一事实感到不舒服。

有更好的方法吗?

7 个答案:

答案 0 :(得分:5)

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提倡的const_cast方法不同,避免重复使用getter,编译器将确保两个公共函数的const正确性。

答案 1 :(得分:4)

我倾向于喜欢简单的解决方案,所以我会选择自由功能方法,可能会添加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实际上很容易实现/ p>

编辑:在编写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 );
}

答案 2 :(得分:3)

由于您的最终实现并不总是相同,我认为没有一个真正的解决方案可以解决您的“问题”。

让我们考虑一下。我们必须迎合Aggregate是const或非const的情况。当然,我们不应该放松它(例如,只提供非const版本)。

现在,运算符的const-version只能调用通过const-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); }

答案 3 :(得分:1)

通常使用这种类型的东西,使用有意义的方法可能更好。例如,load()save()。他们说了一些关于通过访客进行的操作的具体内容。通常情况下都提供了const和非const版本(无论如何都是像访问器这样的版本),所以它看起来只是重复,但是可以节省一些令人头疼的调试。如果你真的想要一个解决方法(我不建议),就是声明方法const和所有成员mutable

答案 4 :(得分:1)

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

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

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