我一直在寻找答案,但无济于事。我的哀叹如下:
我有ClassA
大致如下:
class ClassA : public QObject {
Q_OBJECT
public:
ClassA() { mName = "lol"; }
~ClassA();
void ShowName() { std::cout << mName << std::endl; }
std::string mName;
};
当然,既然我使用了moc,那么这个类实际上在我的项目中被分成了cpp和hpp,但这部分不是问题。
请注意,我不会故意使用Q_DECLARE_METATYPE
,因为我现在实际上并不需要它的功能(QVariant扩展)。我只关心运行时实例化。
这里的问题是Q_OBJECT
禁止复制和赋值构造函数。因此,我必须将qRegisterMetaType
不应用于ClassA
本身,而是应用于ClassA*
乍一看似乎正常。
现在,我想在运行时从字符串动态创建此类并运行方法ShowName()
。我这样做是这样的:
int main() {
qRegisterMetaType<ClassA*>("ClassA*");
int id = QMetaType::type("ClassA*");
std::cout << "meta id: " << id << std::endl; // Outputs correct generated user id (not 0)
ClassA* myclass = static_cast<ClassA*>(QMetaType::construct(id));
myclass->ShowName(); // Segfaults, oh dear
return 0;
}
现在,有我的问题。我似乎并没有真正构造正确的对象。
如果我们将类更改为如下所示:
class ClassA : public QObject {
Q_OBJECT
public:
ClassA() { mName = "lol"; }
ClassA(const ClassA& other) { assert(false && "DONT EVER USE THIS"); }
~ClassA();
void ShowName() { std::cout << mName << std::endl; }
std::string mName;
};
然后我们可以相应地改变我们的程序:
int main() {
qRegisterMetaType<ClassA>("ClassA");
int id = QMetaType::type("ClassA");
std::cout << "meta id: " << id << std::endl; // Outputs correct generated user id (not 0)
ClassA* myclass = static_cast<ClassA*>(QMetaType::construct(id));
myclass->ShowName(); // "lol", yay
return 0;
}
显然我可以使用我的假覆盖复制构造函数,但感觉不对,Qt建议反对,而是建议仅使用指向QObjects的指针。
有人看到这里有什么问题吗?此外,我知道在SO上也存在类似的问题,但没有一个能解决这个问题。
答案 0 :(得分:19)
一些事情:
注册ClassA *不起作用的原因是因为你对construct()的调用是构造一个指向ClassA对象的指针,而不是一个实际的对象。
值得注意的是QMetaType文档中的以下引用:
任何具有公共默认构造函数的公共类或结构 复制构造函数和公共析构函数都可以注册。
看看Qt的qMetaTypeConstructHelper实现:
template <typename T>
void *qMetaTypeConstructHelper(const T *t)
{
if (!t)
return new T();
return new T(*static_cast<const T*>(t));
}
并注意他们对复制构造函数的使用。在这种情况下,您有两种解决问题的方法:
1)提供一个拷贝构造函数(你已经完成)
2)提供不使用复制构造函数的qMetaTypeConstructHelper的特化:
template <>
void *qMetaTypeConstructHelper<ClassA>(const ClassA *)
{
return new ClassA();
}
答案 1 :(得分:11)
如果您想按名称创建QObject
个类的实例,可以使用QMetaObject
代替QMetaType
。
首先,您必须将构造函数声明为invokable:
class ClassA : public QObject {
Q_OBJECT
public:
Q_INVOKABLE ClassA() { mName = "lol"; }
~ClassA();
void showName() { std::cout << mName << std::endl; }
std::string mName;
};
然后,您必须为要实例化的类创建自己的注册系统,并手动填充它:
int main(int argc, char *argv[])
{
// Register your QObject derived classes
QList<const QMetaObject*> metaObjectList;
metaObjectList << &ClassA::staticMetaObject;
// Index the classes/metaobject by their names
QMap<QString, const QMetaObject*> metaObjectLookup;
foreach(const QMetaObject *mo, metaObjectList) {
metaObjectLookup.insert(mo->className(), mo);
}
最后,您将能够通过名称实例化任何已注册的类:
const QMetaObject * myMetaObject = metaObjectLookup.value("ClassA", 0);
if(!myMetaObject)
{
// The class doesn't exist
return 1;
}
ClassA *myObject =
static_cast<ClassA*>(myMetaObject->newInstance());
if(!myObject)
{
// Couldn't create an instance (constructor not declared Q_INVOKABLE ?)
return 1;
}
myObject->showName();
return 0;
}
答案 2 :(得分:6)
以下是针对Qt 5的Chris'解决方案#2的更新:
namespace QtMetaTypePrivate {
template <>
struct QMetaTypeFunctionHelper<ClassA, true> {
static void Delete(void *t)
{
delete static_cast<ClassA*>(t);
}
static void *Create(const void *t)
{
Q_UNUSED(t)
return new ClassA();
}
static void Destruct(void *t)
{
Q_UNUSED(t) // Silence MSVC that warns for POD types.
static_cast<ClassA*>(t)->~ClassA();
}
static void *Construct(void *where, const void *t)
{
Q_UNUSED(t)
return new (where) ClassA;
}
#ifndef QT_NO_DATASTREAM
static void Save(QDataStream &stream, const void *t)
{
stream << *static_cast<const ClassA*>(t);
}
static void Load(QDataStream &stream, void *t)
{
stream >> *static_cast<ClassA*>(t);
}
#endif // QT_NO_DATASTREAM
};
}
如果你的ClassA没有实现运算符&lt;&lt;和运算符&gt;&gt; QDataStream的帮助器,注释掉Save和Load的主体,否则你仍然会遇到编译器错误。