[英]Templated member function and inheritence
我有一个模板成员 function 在 class 中声明,它根据类型调用正确的成员 function,并希望通过添加成员 function 在子代 class 中为其添加一些功能,如下面的 main.cpp 示例所示:
#include <iostream>
class A
{
public:
template <typename T>
void handleSocketData(const T& t)
{
handleData(t);
}
void handleData(int data)
{
std::cout << data << std::endl;
}
};
class B: public A
{
public :
void handleData(std::string data) const
{
std::cout << data << std::endl;
}
};
int main(int argc, char *argv[])
{
A a;
B b;
a.handleSocketData<int>(30);
b.handleSocketData<std::string>("Hi");
return 0;
}
我的问题是b.handleSocketData<QString>("Hi");
实际上确实在 A class 中生成了一个新的模板实例,如命令/usr/bin/clang++ -DQT_CORE_LIB -isystem /usr/include/qt6/QtCore -isystem /usr/include/qt6 -isystem /usr/lib64/qt6/mkspecs/linux-g++ -g -std=gnu++17 -Xclang -ast-print -fsyntax-only main.cpp
:
class A {
public:
template <typename T> void handleSocketData(const T &t) {
this->handleData(t);
}
template<> void handleSocketData<int>(const int &t) {
this->handleData(t);
}
template<> void handleSocketData<std::basic_string<char>>(const std::basic_string<char> &t) {
<recovery-expr>(this->handleData, t);
}
void handleData(int data) {
std::cout << data << std::endl;
}
};
class B : public A {
public:
void handleData(std::string data) const {
std::cout << data << std::endl;
}
};
int main(int argc, char *argv[]) {
A a;
B b;
a.handleSocketData<int>(30);
b.handleSocketData<std::string>("Hi");
return 0;
}
所以现在我有一个编译错误,说没有找到 function handleData(const std::string& data) ,这是正常的。
我们发现的一种解决方法是定义一个双参数模板,将女儿 class 作为参数(一种访问者模式):
#include <iostream>
class A
{
public:
template <typename T, typename U>
void handleSocketData(U& u, const T& t)
{
u.handleData(t);
}
void handleData(int data)
{
std::cout << data << std::endl;
}
};
class B: public A
{
public :
void handleData(std::string data)
{
std::cout << data << std::endl;
}
};
int main(int argc, char *argv[])
{
A a;
B b;
a.handleSocketData<int>(a, 30);
b.handleSocketData<std::string>(b, "Hi");
return 0;
}
你怎么认为? 有没有更清洁的方法?
这看起来像是CRTP的经典用例。 您可以使A
成为派生 class Derived
的模板,然后通过static_cast
将 function 调用分派到派生 class。 为此,任何派生的 class Derived
都必须派生自A<Derived>
。
由于您似乎想将A
用作非抽象 class,因此您必须添加默认派生 class,将其标记为“最终”。 在以下代码中,空结构FinalTag
用于此目的。
#include <iostream>
struct FinalTag;
template <typename Derived=FinalTag>
class A
{
public:
template <typename T>
void handleSocketData(const T& t)
{
cast().handleData(t);
}
void handleData(int data)
{
std::cout << data << std::endl;
}
private:
constexpr auto& cast() {
return static_cast<Derived&>(*this);
}
};
struct FinalTag : A<FinalTag> {};
class B: public A<B>
{
public :
using Base = A<B>;
using Base::handleData;
void handleData(std::string data)
{
std::cout << data << std::endl;
}
};
int main(int argc, char *argv[])
{
A a;
B b;
a.handleSocketData(30);
b.handleSocketData("Hi");
// this only works if you bring in Base::handleData in the
// derived class
b.handleSocketData(30);
return 0;
}
直播代码: https://godbolt.org/z/ns9aPjG76
这是一个原型。 例如,您可能希望向cast
方法添加一个 const 版本。
正如 Jarod42 在评论中指出的那样,C++23 通过“推导这个”真正简化了 CRTP: https://godbolt.org/z/cGzMrnEhc 。 不过,目前编译器并未广泛支持这一点。
在某些情况下,与 Joerg Brech 建议的版本略有不同的 CRTP 版本可能更合适。
#include <iostream>
class A
{
public:
template <class Class, typename T>
void handleSocketData(const T& t)
{
static_cast<Class*>(this)->handleData(t);
}
void handleData(int data)
{
std::cout << data << std::endl;
}
};
class B: public A
{
public :
void handleData(std::string data) const
{
std::cout << data << std::endl;
}
};
int main(int argc, char *argv[])
{
A a;
B b;
a.handleSocketData<A, int>(30);
b.handleSocketData<B, std::string>("Hi");
return 0;
}
它与您的解决方案非常相似,因为我们指示handleSocketData
它应该使用 class 从中调用handleData
。 唯一的区别是决定不是动态的而是在编译时做出的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.