我遇到了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
这一事实感到不舒服。
有更好的方法吗?
答案 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_if
,is_same
和remove_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);
}