可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have a class with implements 2 interfaces and inherits 1 class. So, generally it looks like this:
class T : public A, public IB, public IC {
};
There is one point in the code where I have an IB *
, but could really use an A *
. I was hoping that a dynamic cast would like this:
IB *b_ptr = new T; // it's really more complicated, but serves the example
A *a_ptr = dynamic_cast<A *>(b_ptr);
unfortunately, this doesn't work. Is there a proper way to do this? Or should I implement a work around? I've thought about having both IB
and IC
inherit virtually from A
, but IIRC last time I tried that there were some complications that made it undesirable.
Any thoughts?
EDIT: oh yea, this is part of a plugin API, so unfortunately I don't have direct access to the T
type where I need the A *
. My example has then next to each other, but as mentioned, it's more complicated. Basically I have 2 shared libraries. T
and T1
(where I have an IB *
) are both classes which implement a plugin API and are internal to the shared libraries.
To clarify: Here's a more specific example of my typical plugins (they are in separate libraries):
plugin A:
class PluginA : public QObject, public PluginInterface, public OtherInterface {
};
plugin B:
class PluginB : public QObject, public PluginInterface {
// in here, I have a PluginInterface *, but really could use a QObject *
// unfortunately, PluginB has absolutely no knowledge of the "PluginA" type
// it just so happens that my PluginInterface * pointer points to an object of type
// PluginA.
};
EDIT: I have a guess that the issue is that pluginA and pluginB are in different shared libraries. Perhaps the rtti doesn't cross module boundaries. I think this might be the case because people's examples seem to work fine in my tests. Specifically, pluginB has no "typeinfo for PluginA" if I do an "nm" on it. This may be the core of the issue. If this is the case, I'll simply have to work around it by either virtual inheritance or a virtual cast_to_qobject()
function in one of my interfaces.
回答1:
Does each class have at least one virtual method? If not, there's your problem. Adding a virtual destructor to each class should overcome the problem.
The following happily worked for me:
class IC
{
public:
virtual ~IC() {}
};
class IB
{
public:
virtual ~IB() {}
};
class A
{
public:
virtual ~A() {}
void foo() { /* stick a breakpoint here to confirm that this is called */ }
};
class T : public A, public IB, public IC
{
public:
virtual ~T() {}
};
int main(void)
{
IB *b_ptr = new T;
A *a_ptr = dynamic_cast<A *>(b_ptr);
a_ptr->foo();
return 0;
}
EDIT:
After all the new info, and the unusual behavior (your code should just work!), does the following help? I've introduced an interface called IObject and use virtual inheritance to ensure that there is only one copy of this base class. Can you now cast to IObject and then to A?
class IObject
{
public:
virtual ~IObject() {}
};
class IC : virtual public IObject
{
public:
virtual ~IC() {}
};
class IB : virtual public IObject
{
public:
virtual ~IB() {}
};
class A : virtual public IObject
{
public:
virtual ~A() {}
void foo() { /* stick a breakpoint here to confirm that this is called */ }
};
class T : virtual public A, virtual public IB, virtual public IC
{
public:
virtual ~T() {}
};
int main()
{
IB *b_ptr = new T;
A *a_ptr = dynamic_cast<A *>( dynamic_cast<IObject *>(b_ptr) );
a_ptr->foo();
return 0;
}
I'm not suggesting that it's the right solution, but it might offer some info as to what's going on...
回答2:
Is there a proper way to do this? Or should I implement a work around? I've thought about having both IB and IC inherit virtually from A, but IIRC last time I tried that there were some complications that made it undesirable.
I take it then that the definitions of IB and IC are under your control.
There's the way in which COM interfaces work on Windows; these do what you are wanting want to do, i.e.:
- Cast from one interface to another
- Implementation is opaque to the caller
- Only the implementation knows which interfaces it implements
Do do this, you can do something like (untested code ahead) ...
interface IQueryInterface
{
IQueryInterface* queryInterface(const Guid* interfaceId);
};
interface IB : public abstract IQueryInterface
{
...
};
interface IC : public abstract IQueryInterface
{
...
};
//within your implementation class
IQueryInterface* T::queryInterface(const Guid* interfaceId)
{
if (matches(interfaceId,GUID_IB))
return (IB*)this;
if (matches(interfaceId,GUID_IC))
return (IC*)this;
if (matches(interfaceId,GUID_A))
return (A*)this;
return 0;
}
A much simpler, more hard-coded version of this would be:
class A; //forward reference
interface IB
{
virtual A* castToA() { return 0; }
};
class T : public A, IB, IC
{
virtual A* castToA() { return this; }
};
回答3:
Cast to a T* first then to A:
IB *b_ptr = new T; // it's really more complicated, but serves the example
A *a_ptr = dynamic_cast<T *>(b_ptr);
If IB in general should be castable to A, then maybe IB should inherit from A.
Edit:
I just tried this and it works - note that E is unknown at the time of compiling the main method.
struct A
{
virtual ~A() {}
};
struct C
{
virtual ~C() {}
};
A* GetA();
int main()
{
C *y = dynamic_cast<C *>(GetA());
if (y == NULL)
cout << "Fail!";
else
cout << "Ok!";
}
struct E : public A, public C
{
};
A* GetA() { return new E(); }
回答4:
I finally figured it out, Daniel Paull was correct in that a "sideways dybnamic_cast
" should be allowed. My problem was because my code is involving shared libraries. The typeinfo from PluginA was not available in PluginB. My solution was to effectively add RTLD_NOW
and RTLD_GLOBAL
to my load process
technically it was
loader.setLoadHints(QLibrary::ResolveAllSymbolsHint | QLibrary::ExportExternalSymbolsHint);
because I'm using Qt's plugin system but same difference. These flags force all symbols from loaded libraries to be resolved immediately and be visible to other libraries. Therefore making the typeinfo available to everyone that needed it. The dynamic_cast
worked as expected once these flags were in place.
回答5:
I've also recently been bothered with the same kind of problem. For more information, see GCC's FAQ entry:
http://gcc.gnu.org/faq.html#dso
Besides instructing dlopen with RTLD_* flags, some incarnations of this problem can be solved by the linker as well, see its -E and -Bsymbolic options.