我有一个类的多态层次结构。虽然我也支持标准的工厂方法,我只使用基类指针,但我也想要一个工厂机制,它给我派生类,这并不容易,因为这些函数的返回类型不同。这就是为什么我想出了重载函数的想法,让编译器选择正确的函数。
这个的简单应用是我可以编写创建派生对象的函数,"准备"它,并返回一个指向它的基指针,以便在不再需要类型信息时进一步访问。
p1
。我在newObject()
中设置之前从未读过它的值,但我不确定它是否会导致未定义的行为。我也有自我赋值,因为返回的值被赋给了自己...... 这是代码示例:
class Base
{
public:
virtual ~Base(void){}
};
class Derived1 : public Base
{
public:
virtual ~Derived1(void){}
};
class Derived2 : public Base
{
public:
virtual ~Derived2(void){}
};
// factory
Base * newObject(int i)
{
if (i == 1)
{
return new Derived1();
}
else
{
return new Derived2();
}
}
// family of functions to create all derived classes of Base
Derived1 * newObject(Derived1 *& p)
{
p = new Derived1();
return p;
}
Derived2 * newObject(Derived2 *& p)
{
p = new Derived2();
return p;
}
int main()
{
// compiler picks the right newObject function according to the parameter type
Derived1 * p1 = newObject(p1);
Derived2 * p2 = newObject(p2);
// This is safe, right? But it does not convey that it creates something. Hence I prefer the above syntax.
Derived2 * p3 = nullptr;
newObject(p3);
delete p3;
delete p2;
delete p1;
}
修改
为了避免使用刚刚创建的变量的问题,这是另一种选择:
Derived1 * newObject(Derived1 *)
{
// anonymous variable is not used at all, just to pick the right function
Derived1 * p = new Derived1();
return p;
}
答案 0 :(得分:4)
这是一个值得怀疑的设计,但从纯粹的语言角度来看是正确的。 C ++标准有这样说:
1对象或引用的生命周期是的运行时属性 对象或参考。据说一个物体是非空的 初始化,如果它是类或聚合类型和它或其中之一 它的子对象由一个非常重要的构造函数初始化 默认构造函数。类型T对象的生命周期始于:
- 获得具有类型T的适当对齐和尺寸的存储,并且
- 如果对象具有非空的初始化,则其初始化完成,
...
7在对象的生命周期开始之前但在存储之后 对象将占用的对象将被分配,或者在生命周期之后 一个对象已经结束并且在存储对象之前 占用被重用或释放,任何引用的glvalue 可以使用原始对象,但仅限于有限的方式......例如glvalue 指分配的存储,并使用glvalue的属性 不依赖于它的价值是明确的。该计划有 未定义的行为如果:
- glvalue用于访问对象,或
- ...
第7段明确规定使用这样的引用来分配到其生命周期尚未开始的对象是UB。
但根据第1段,指针的生命周期在为其分配存储时开始,因为它是具有空初始化的类型。所以从本质上讲,您的代码不受第7段的威胁。
如果您不想在调用newObject
时指定模板参数(如果使用C ++ 11 auto p = newObject<Dervied1>()
,则不是那么糟糕或详细),并且您可以访问C至少++ 11,您可以使用常见的模板相关技巧来解决构造问题,直到知道类型为止。它的肉:
namespace detail {
// ...
template<typename... Args>
struct DefferedConstruct {
std::tuple<Args&...> args_tuple;
template<typename T>
operator T*() {
return new T(make_from_tuple<T>(
args_tuple
));
}
};
} // namespace detail
template<typename... Args>
auto newObject(Args&&... args) -> detail::DefferedConstruct<Args...> {
return detail::DefferedConstruct<Args...>{
std::forward_as_tuple(args...)
};
}
class Base
{
public:
virtual ~Base(void){}
};
class Derived1 : public Base
{
public:
Derived1(int) {}
virtual ~Derived1(void){}
};
class Derived2 : public Base
{
public:
virtual ~Derived2(void){}
};
int main()
{
// compiler construct the right object according to the return type
Derived1 *p1 = newObject(1);
Derived2 *p2 = newObject();
delete p1;
delete p2;
}
上面的代码只是使用大量的C ++魔法来加载构造函数参数,直到在函数出口处知道要构造的类。
答案 1 :(得分:2)
我认为您编写的代码是安全的。
要创建派生类型的工厂,因为无法在返回类型上重载, 为什么不只是有不同的命名方法?调用者必须知道它需要什么类型才能声明变量,所以要求他们也知道调用哪种工厂方法并不太麻烦。 E.g:
Derived1 * newDerived1() {
return new Derived1();
}
Derived2 * newDerived2() {
return new Derived2();
}
如果必须具有相同的工厂方法,则可以使用模板:
template<
typename T,
typename = std::enable_if_t<std::is_base_of<Base, T>::value>
>
T* newObject() {
return new T();
}
Derived1* d = newObject<Derived1>();
如果在调用构造函数之前所需的设置代码对于每个派生类都不同,或者如果每个派生类的构造函数采用不同的参数,则可以使用模板特化来覆盖它。
但请注意,在调用工厂时,您仍需要提供作为模板参数创建的类型。据我所知,目前还没有办法解决这个问题。