问题描述
我有一个多态的类层次结构.虽然我还支持标准的工厂方法,在此方法中,我仅使用基类指针,但我还希望有一种工厂机制为我提供派生类,这并不容易,因为这些函数的返回类型不同.这就是为什么我想到重载函数并让编译器选择正确的函数的原因.
I have a polymorphic hierarchy of classes. While I also support the standard factory approach, where I use only base class pointers, I also want a factory mechanism which gives me derived classes, which is not easy because these functions only differ in their return types. This is why I came up with the idea to overload a function and let the compiler pick the right one.
对此的一个简单应用是,我可以编写一些函数来创建派生对象,准备"它,并在不再需要类型信息时返回指向它的基址指针以进行进一步访问.
A simple application of this is that I can write functions which create a derived object, "prepare" it and return a base pointer to it for further access, when the type information is not needed anymore.
- 问题1:以下内容可以吗?
- 问题2:newObject()的参数仅用于编译器选择正确的函数.我担心在声明它的同一行上使用
p1
.在newObject()
中设置它之前,我从未读取过它的值,但是我不确定它是否会导致未定义的行为.我也有一个自我分配,因为返回的值分配给自己...
- Question 1: Is the following ok?
- Question 2: The parameter of newObject() is only for the compiler to pick the right function. I am concerned about using
p1
on the same line where it is declared. I never read its value before setting it innewObject()
, but I am not sure if it can cause undefined behavior. Also I have a self-assignment because the returned value is assigned to itself...
这是代码示例:
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;
}
为避免使用刚刚创建的变量的问题,这是一种替代方法:
To circumvent the problem of using a variable which was just created, this is an alternative:
Derived1 * newObject(Derived1 *)
{
// anonymous variable is not used at all, just to pick the right function
Derived1 * p = new Derived1();
return p;
}
推荐答案
这是一个有问题的设计,但从纯语言的角度来看是正确的. C ++标准有这样的说法:
It's a questionable design, but correct from a pure language standpoint. The C++ standard has this to say:
- 获得具有正确对齐方式和大小的T型存储,并且
- 如果对象具有非空初始化,则其初始化完成,
...
7在对象生命周期开始之前但在存储之后 对象将占用的对象已被分配,或在生存期之后 的对象已结束,并且在存储对象之前 占用将被重用或释放,任何引用该值的glvalue 可以使用原始对象,但只能以有限的方式使用...这样的glvalue 指分配的存储,并使用glvalue的属性 不依赖其值的定义是明确的.该程序有 未定义的行为,如果:
7 before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, any glvalue that refers to the original object may be used but only in limited ways... such a glvalue refers to allocated storage, and using the properties of the glvalue that do not depend on its value is well-defined. The program has undefined behavior if:
- glvalue用于访问对象,或
- ...
第7段明确规定,使用此类引用将尚未分配生命的对象分配给UB.
Paragraph 7 explicitly specifies that using such a reference to assign into an object whose lifetime hasn't started yet is UB.
但是根据第1款,指针的生存期是在为其分配存储空间后立即开始的,因为它是具有空虚初始化的类型.因此,从本质上讲,您的代码不受第7段的威胁.
But according to paragraph 1, the lifetime of the pointer starts right when storage is allocated for it, since it is a type with vacuous initialization. So in essence, your code isn't under the threat of paragraph 7.
如果您不想在调用newObject
时指定模板参数(如果使用C ++ 11 auto p = newObject<Dervied1>()
则不是那么糟糕或冗长),并且可以在以下位置访问C ++ 11至少,您可以使用与模板相关的通用技巧来推迟构造,直到知道类型为止.它的肉:
If you don't want to specify the template parameter when calling newObject
(which isn't so bad or verbose if you use C++11 auto p = newObject<Dervied1>()
), and you have access to C++11 at the very least, you can use a common template related trick to deffer the construction until the type is known. The meat of it:
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 ++魔术来拖延构造函数的参数,直到在函数出口知道要构造的类为止.
The code above just uses a lot of C++ magic to lug the constructor parameters around until the class to construct is known at function exit.
这篇关于我可以在C ++中在工厂中声明的同一行上使用变量吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!