在类模板中消除基类名称的歧义
disambiguate base class name in class template
我们有一个日志框架,它允许我们将上下文特定的信息自动添加到日志消息中。
这是通过继承Context
类模板实现的,该模板使用CRTP pattern
/static polymorphism
,在子类上调用Token()
函数来获得对象的上下文。
在日志记录时,我们使用扩展为GetLog()
的宏。
- 当成员函数在基类中找到
GetLog
时,它获取记录器并添加上下文Token
。 - 当在自由函数或非
Context
派生类中,GetLog
在全局命名空间中被找到,并且它只获得记录器。
下面是一个完整的工作示例:
我的问题是,如果我有一个类模板,这是反过来衍生的Context
,例如:
template<typename T>
struct Foo : Context<Foo<T>>;
当我尝试在成员函数内登录时,我必须用this->
前缀LOG
,以便使方法GetLog()
成为依赖的名称。
是否有任何方法可以让类模板成员函数使用LOG
而不使用this->
并将其解析为Context::GetLog()
?
- 将
Token
设为虚函数,并使用动态多态性获取上下文。缺点是这会在每个日志调用上增加一个v表查找(对于大量Context
派生对象,编译器能够内联吗?)我想让它尽可能精简。 - 使用
std::enable_if
和std::is_base_of
来区分Context
和non-Context
派生对象。我不认为我能让这个和自由函数一起工作? - 还有其他办法吗?
下面是一个工作示例:
#include <iostream>
// stub logging object to make the example work - just logs to stdout
struct Log
{
template<typename T>
friend Log& operator<<(Log& l, T d)
{
std::cout << d;
return l;
}
friend Log& operator<<(Log& l, std::ostream& (*f)(std::ostream&))
{
std::cout << f;
return l;
}
};
Log gLog;
#define LOG GetLog()
// GetLog in the global namespace for non-Context derived classes, free functions etc
Log& GetLog()
{
return gLog;
}
// classes derive from this to add context specific information when logging
template<typename Self>
struct Context
{
// this GetLog adds prefix to Context derived classes
Log& GetLog()
{
static_cast<const Self*>(this)->Token(gLog); // add the Context's Token to the log
return gLog << ": ";
}
};
//-------------------------
template<typename T>
struct Foo : Context<Foo<T>>
{
void Func1()
{
LOG << __func__ << std::endl; // resolves to the global GetLog() free-function
}
void Func2()
{
this->LOG << __func__ << std::endl; // notice the 'this->' prefix to make GetLog() a dependent name
}
Log& Token(Log& l) const { return l << "Foo"; }
};
// logging inside a non-Context derived class
struct Bar
{
void Func()
{
LOG << __func__ << std::endl;
}
};
// logging inside a free function
void Baz()
{
LOG << __func__ << std::endl;
}
//-------------------------
int main()
{
Foo<int> f;
f.Func1();
f.Func2();
Bar b;
b.Func();
Baz();
exit(0);
}
您可以使用声明显式地将Context::GetLog
引入派生类的作用域:
template<typename T>
struct Foo : Context<Foo<T>>
{
using Context<Foo<T>>::GetLog;
// the rest as before
};
生活例子
嗯,有一个简单的解决方案,尽管它需要在上下文派生类中做一些额外的工作。
您可以通过基类名称而不是this->
来使用限定,因此,例如使用Context<Foo<T>>::GetLog
足以向编译器表明查找是依赖的(并且必须延迟到实例化)。
不幸的是,由于Context
本身就是一个模板,它有点无聊;所以我们将使用另一个类(这里我称之为Base
):
// 1. Wrap the generic GetLog into a `Base` class:
struct Base {
static Log& GetLog() { return glog; }
};
// 2. Introduce a typedef into the derived class:
template <typename T>
struct Foo: Context<Foo<T>> {
typedef Context<Foo<T>> Base;
...
};
// 3. Change the macro
#define LOG Base::GetLog()
这里,Base
查找遵循典型的作用域规则,并找到最接近的Base
…在最坏的情况下,默认为全局作用域中存在的那个。
相关文章:
- std::具有相同基类的类的变体
- 是否可以初始化不可复制类型的成员变量(或基类)
- 在C++中,是否可以基于给定的标识符创建基类的新实例,反之亦然
- 基类中的函数名称解析
- C++初始化基类
- 如何通过派生类函数更改基类中的向量
- 如何定义一个纯抽象基类
- 如何使用基类指针引用派生类成员
- 继承:构造函数,初始化C++11中基类的类C数组成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 如何引用基类的派生类?
- 如果基类包含双指针成员,则派生类的构造函数
- 在模板基类中为继承类中的可选重写生成虚拟方法
- 为什么此派生对象无法访问基类的后递减方法?
- 公开最直接的基类模板名称
- 当基类是依赖类型时,这是一个缺陷吗
- 警告:由于歧义而导致的直接基类无法访问;这是严重的
- 在将派生类指针转换为基类时,基类有歧义
- 在类模板中消除基类名称的歧义
- 多重继承歧义基类