假设我有一个派生自基类的类。多态允许我通过指向基类的指针(使用虚函数)调用派生类的函数。换句话说,我可以让派生类的成员“假装”成为基类的成员。记住这一点,基类是否有可能在其构造函数中实际构造派生类的成员(然后“假装”成为基类的成员,因此没有任何中断)?从逻辑上讲,我认为没有理由这不起作用,但我无法弄清楚如何在语法上做到这一点。如果这不可能,为什么不呢?
我知道我可以有一个单独的函数来构造派生类的成员并将其作为基类的成员返回。如果这最终变得不可能,那就是我将要做的事情,但是将它作为构造函数更加清晰(因为这就是这个单独函数的基本原理)。
编辑:这是我正在寻找的一个例子:
class base
{
base()
{
this=new derived(); //This is what I am looking for
}
virtual func();
};
class derived : public base
{
derived() : base()
{}
func()
{
...
}
};
如上所述,这可以通过以下方式实现:
base *fake_base_constructor()
{
return new derived();
}
实际上,会有多个派生类,基类构造函数会根据参数在它们之间进行选择,但从概念上讲,您只需要一个。
答案 0 :(得分:4)
听起来你想要curiously recurring template pattern。它允许基类通过为其提供模板参数(即派生类型)来了解从中派生的类型:
template <class D>
class Base
{ };
class Derived : public Base<Derived>
{ };
现在,您可以在Derived
的定义中使用模板参数Base
。例如,Base
的构造函数可以执行:
template <class D>
void Base<D>::some_base_member()
{
D d;
};
事实上,此模式的一个重要结果是您可以从Derived
类调用Base
成员。例如:
template <class D>
void Base<D>::some_base_member()
{
static_cast<D*>(this)->some_derived_member();
};
答案 1 :(得分:2)
听起来你想通过将一些东西传递给构造函数来选择你得到的东西的类型?您的示例给出了静态选择,但文本暗示将有多个选择。我很好奇你打算如何使用它,特别是。
说你想做这样的事情:
enum Types
{
Type_A,
Type_B,
};
class Base
{
public:
Base(Types t)
{
switch (t)
{
case Type_A:
this = <magic> A;
break;
case Type_B:
this = <magic> B;
break;
}
}
};
但问题是这不是C ++的工作方式;您的对象的空间已经在构造函数被命中时分配,因此您不能将派生项填充到该空间中。例如,假设您将此作为类成员变量:
struct SomeStruct
{
int i;
Base b;
float f;
SomeStruct();
}
SomeStruct::SomeStruct() : i(4), b(Type_A), f(3.14f)
{
}
这只是行不通。听起来最简单的解决方案是使用工厂功能;它明确表示你必须处理指针,这是程序员习惯看到的东西:
Base* BaseFactory(Types t)
{
switch (t)
{
case Type_A:
return new A;
case Type_B:
return new B;
}
}
你可以用奇怪的重复模板模式做类似的事情,但它并不像另一个答案所建议的那样直接(部分原因是因为你可能想要维基百科文章中没有解决的运行时多态性),但这并不是必需的;无论你的解决方案是什么,构建这些东西的代码模块都必须知道可以创建的所有东西,并且考虑到约束它可能最好在基类之外的地方拥有那些知识束。只是我的意见,但我认为从长远来看,最安全的做法是避免构建大量的字符串。
答案 2 :(得分:0)
你问:
“基类在构造函数中是否有可能实际构造派生类的成员(然后”假装“成为基类的成员,因此没有任何中断)?”
是的,有很多方法可以在基类中进行派生类特定的初始化。
以下是我撰写的一篇博文中的一个例子,名为“How to avoid post-construction by using Parts Factories”:
#include <stdio.h>
namespace apiLevel {
enum Handle {};
Handle newButton( char const title[] )
{
printf( "apiLevel::newButton(\"%s\")\n", title );
return Handle();
}
Handle newListbox( char const [] = "" ) { return Handle(); }
Handle newStatic( char const [] = "" ) { return Handle(); }
} // namespace apiLevel
class Widget
{
private:
apiLevel::Handle handle_;
protected:
struct ApiWidgetFactory
{
virtual apiLevel::Handle newWidget( char const title[] ) const
{
return apiLevel::newStatic( title ); // Reasonable.
}
};
public:
explicit Widget(
char const title[] = "\"? (unspecified)\"",
ApiWidgetFactory const& factory = ApiWidgetFactory()
)
: handle_( factory.newWidget( title ) )
{}
};
class Button
: public Widget
{
protected:
struct ApiWidgetFactory: Widget::ApiWidgetFactory
{
virtual apiLevel::Handle newWidget( char const title[] ) const
{
return apiLevel::newButton( title ); // Derived class specific.
}
};
public:
explicit Button(
char const title[],
ApiWidgetFactory const& factory = ApiWidgetFactory()
)
: Widget( title, factory )
{}
};
int main()
{
Button button( "Just a button" );
}
上述方法的主要优点是它为客户端代码提供了一个类型安全接口,具有普通的面向RAII的构造(即,充分利用了类不变量)。
主要的缺点是它需要维护两个并行的类层次结构,即使这主要是在何处放置代码的问题。
有关详细信息,请参阅该博客帖子,该帖子还会进一步链接到the FAQ,其中还介绍了其他一些方法。博客文章有点直接来自马的嘴,即我的。常见问题项目是我说服Marshall写下这个问题的结果。常见问题解答。