【发布时间】:2021-11-14 02:39:19
【问题描述】:
我知道这个问题被问了很多,但我有一个特定的用例,所以我不认为它是重复的!
我有一个抽象基类:
template<int N>
class Child;
class Base
{
public:
// Factory-like generation of children as Base
static Ptr<Base> New(int baseN)
{
if (baseN == 2) return new Child<2>();
else if (baseN == 3) return new Child<3>()
}
// Update
virtual void update() = 0;
};
我正在编写 Base 的一些子类作为类模板(在 int 上):
template<int N>
class Child
:
public Base
{
// Member, N is not the size of matrix, more like the size of a component in matrix
Matrix<N> m_member;
public:
// Implement update
virtual void update();
// Should call the passed callable on m_member
virtual void execute(std::function<void(Matrix<N>&)>&);
};
// Force compilation of Child<N> for some values of N (of interest, including 3) here
// Then,
int baseN = 3;
Ptr<Base> obj = Base::New(baseN); // will get me a Child<3> as a Base object
auto callable = [](Matrix<3>) ->void {};
// Can I access Child<3>::m_member ??
// Can't cast to Child<baseN> (baseN is not constexpr) and don't want to
// But want to do something like:
obj->execute(callable);
// Which forwards 'callable' to the method from concrete type, probably using a cast?
简而言之,我需要从声明的Base 对象中访问m_member。
最好是一种从Base 调用Child<N>::execute 的方法,而无需将Base 也作为N 上的模板。
我尝试过/想过的事情包括:
- 通过将
Matrix<N>的“类型擦除”隐藏在接口后面,但是由于Matrix<N>的接口强烈依赖于N,这样做会使类变得无用(例如:Vector<N>& Matrix<N>::diag()) -
Base::New可以做任何事情来记录它创建的具体类型吗?我对此表示怀疑,因为类型不是对象。
编辑:(顺便说一句,这是 C++11)
所以,我不小心想出了一个办法来做到这一点;但我不完全理解为什么以下工作(还不精通组装):
- 我正在使用对象数据库(
unordered_map<string, object*>其中object是每个注册对象都必须继承的类)。 - 创建 Child 后,我们将其注册到名称为
Child<N>的数据库中。 - 然后,在应用程序级代码中,有一个
findChild<int N>模板,它使用编译时递归来查找从哪个具体类创建的基指针(在运行时,通过动态转换和测试)。当它找到它时,它可以通过静态方法将它转换为void*(findChild<N>::castToConcrete) - 有趣的是,如果
Child<N>是多态的,我们可以以某种方式使用findChild<0>来访问有问题的findChild<N>。这迫使我们最多有一个 Child 对象(对于所有可能的 N),我当然可以接受。
您可以在此处查看和检查最小代码示例:https://onlinegdb.com/CiGR1Fq5z
我很困惑的是Child<0> 和其他Child<N> 是完全不同的类型;那么我们如何从一个指向另一个类型的指针访问一个成员呢?我很可能依赖于 UB,甚至担心会出现某种堆栈问题!
作为参考,我在此处包含代码以防链接失效。
#include <unordered_map>
#include <vector>
#include <functional>
#include <iostream>
using namespace std;
#ifndef MAX_N_VALUE
#define MAX_N_VALUE 10
#endif // !MAX_N_VALUE
// ------------------ Lib code
// A dummy number class for testing only
template <int N> struct Number { constexpr static int value = N; };
// Objects to register to the database
struct object
{
// Members
string name;
// construction/Destruction
object(const string& name) : name(name) {}
virtual ~object(){};
};
// Database of objects
struct DB
: public unordered_map<string, object*>
{
// See if we can the object of name "name" and type "T" in the DB
template <class T>
bool found(const string& name) const
{
unordered_map<string,object*>::const_iterator iter = find(name);
if (iter != end())
{
const T* ptr = dynamic_cast<const T*>(iter->second);
if (ptr) return true;
cout << name << " found but it's of another type." << endl;
return false;
}
cout << name << " not found." << endl;
return false;
}
// Return a const ref to the object of name "name" and type "T" in the DB
// if found. Else, fails
template <class T>
const T& getObjectRef(const string& name) const
{
unordered_map<string,object*>::const_iterator iter = find(name);
if (iter != end())
{
const T* ptr = dynamic_cast<const T*>(iter->second);
if (ptr) return *ptr;
cout << name << " found but it's of another type." << endl;
abort();
}
cout << name << " not found." << endl;
abort();
}
};
// Forward declare children templates
template<int N>
class Child;
// The interface class
struct Base
{
// Construction/Destruction
protected:
static unsigned counter;
Base(){}
public:
virtual ~Base() {}
// Factory-like generation of children as Base
// THIS New method needs to know how to construct Child<N>
// so defining it after Child<N>
static Base* New(int baseN, DB& db);
// Update
virtual void update() = 0;
// Call a callable on a child, the callable interface
// however is independent on N
virtual void execute(std::function<void(Base&)>& callable)
{
callable(*this);
}
};
unsigned Base::counter = 0;
// The concrete types, which we register to the DB
template<int N>
struct Child
:
public Base, public object
{
// members
vector<Number<N>> member;
// Construction/Destruction
Child() : Base(), object(string("Child") + to_string(N) + ">"), member(N, Number<N>()) {}
virtual ~Child() {}
// Test member method (Has to be virtual)
virtual vector<Number<N>> test() const
{
cout << "Calling Child<" << N << ">::test()" << endl;
return vector<Number<N>>(N, Number<N>());
}
// Implement update
virtual void update()
{
cout << "Calling Child<" << N << ">::update()" << endl;
};
};
// New Base, This can be much more sophisticated
// if static members are leveraged to register constructors
// and invoke them on demand.
Base* Base::New(int baseN, DB& db)
{
if (baseN == 2)
{
Child<2>* c = new Child<2>();
db.insert({string("Child<")+std::to_string(2)+">", c});
return c;
}
if (baseN == 3)
{
Child<3>* c = new Child<3>();
db.insert({string("Child<")+std::to_string(3)+">", c});
return c;
}
return nullptr;
}
// Finder template for registered children
template<int N>
struct findChild
{
// Concrete Type we're matching against
using type = Child<N>;
// Stop the recursion?
static bool stop;
// Compile-time recursion until the correct Child is caught
// Recursion goes UP in N values
static void* castToConcrete(const DB& db, Base* system)
{
if (N > MAX_N_VALUE) stop = true;
if (stop) return nullptr;
if (db.found<type>(string("Child<")+to_string(N)+">"))
{
type* ptr = dynamic_cast<type*>(system);
return static_cast<void*>(ptr);
}
// NOTE: This should jump to the next "compiled" child, not just N+1, but meh;
return findChild<N+1>::castToConcrete(db, system);
}
};
// Activate recursive behaviour for arbitraty N
template<int N>
bool findChild<N>::stop = false;
// Explicit specialization to stop the Compile-time recursion at a decent child
template<>
struct findChild<MAX_N_VALUE+1>
{
using type = Child<MAX_N_VALUE+1>;
static bool stop;
static void* castToConcrete(const DB& t, const Base* system)
{
return nullptr;
}
};
// Disactivate recursive behaviour for N = 11
bool findChild<MAX_N_VALUE+1>::stop = true;
// ------------------ App code
int main()
{
// Create objects database
DB db;
// --- Part 1: Application writers can't write generic-enough code
// Select (from compiled children) a new Base object with N = 2
// and register it to the DB
Base* b = Base::New(2, db);
b->update();
cout << "Access children by explicit dynamic_cast to Child<N>:" << endl;
// Get to the object through the objects DB.
// Child destructor should remove the object from DB too, nut meh again
const auto& oo = db.getObjectRef<Child<2>>("Child<2>");
cout << oo.test().size() << endl;
// --- Part 2: Application writers can write generic code if the compile
// Child<N> for their N
cout << "If Child<N> is polymorphic, we can access the correct child from findChild<0>:" << endl;
// Create a lambda that knows about db, which Base applies on itself
function<void(Base&)> lambda = [&db](Base& base) -> void {
// Cast and ignore the result
void* ptr = findChild<0>::castToConcrete(db, &base);
// Cast back to Child<0>
findChild<0>::type* c = static_cast<findChild<0>::type*>(ptr);
// Now access original Child<N> methods and members from Child<0>
cout << "Method:\n" << c->test().size() << endl;
cout << "Member:\n" << c->member.size() << endl;
};
b->execute(lambda);
return 0;
}
我使用 GCC 9 编译,带有以下选项:
-m64 -Wall -Wextra -Wno-unused-parameter -Wold-style-cast -Wnon-virtual-dtor -O0 -fdefault-inline -ftemplate-depth-200
【问题讨论】:
-
因为您甚至无法联系到
execute? -
是的,因为参数依赖于“Matrix
”上的可调用对象 -
如果您将minimal reproducible example 与错误消息一起发布,问题会更清楚。例如
Ptr是什么?另外我建议一次解决一个问题。我不知道你想如何通过调用Base::New(baseN);创建Child<3>,然后我已经迷路了 -
Ptr 只是智能指针类的占位符,我无法构建 MRE,因为 new() 函数依赖于很多其他东西,+ 我什至还没有处于得到错误:)
-
不管是 lambda 还是任何其他类型的可调用。所以一些客户端代码提供了可调用的。 那个代码怎么知道N是什么?
标签: c++ oop templates multiple-inheritance