C++模板来覆盖常量和非常量方法
C++ template to cover const and non-const method
对于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_if
、is_same
和remove_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);
}
- 为什么常量方法可以采用非常量引用?
- 常量方法中的非常量 lambda 捕获
- Gmock 常量方法不调用,而是调用原始方法
- 为什么我们需要常量方法?
- 模板常量/非常量方法
- 在常量方法中调用非常量方法
- 如何让编译器在C++中更喜欢常量方法重载?
- 常量方法中 decltype 的结果
- 如何从QDialog的常量方法显示QMessageBox?
- 常量方法指针的类型是什么
- C++类的常量方法中将字符数组分配给 T* 的方法
- 在公共常量方法中访问私有成员
- 常量方法中的奇怪行为,其中变量可以修改
- 当函数引用对象(并访问非常量方法)时,如何抛弃常量?
- 非常量指针成员上的C++常量方法
- 从 const 方法调用成员上的非常量方法
- "Reference qualifier correctness"还是应该将非常量方法应用于右值?
- 在Turtle中为常量方法创建Mock
- C++模板来覆盖常量和非常量方法
- 单元测试-在C++TDD中调用重载常量与非常量方法的好方法