我可以在 C++ 工厂中声明的同一行中使用变量吗?
Can I use a variable on the same line it is declared in this factory in C++?
我有一个 classes 的多态层次结构。虽然我也支持标准工厂方法,我只使用基 class 指针,但我还想要一个工厂机制,它给我派生 classes,这并不容易,因为这些函数仅在它们的 return 类型。这就是为什么我想出重载函数并让编译器选择正确的想法的原因。
一个简单的应用是我可以编写创建派生对象的函数,"prepare" 它和 return 指向它的基指针以便在不需要类型信息时进一步访问没有了。
- 问题一:下面的可以吗?
- 问题2:newObject()的参数只是为了让编译器选择正确的函数。我担心在声明它的同一行上使用
p1
。在 newObject()
中设置它之前,我从未读取过它的值,但我不确定它是否会导致未定义的行为。我还有一个自我赋值,因为 returned 值被赋给了它自己...
这是代码示例:
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;
}
这是一个有问题的设计,但从纯语言的角度来看是正确的。 C++ 标准是这样说的:
1 The lifetime of an object or reference is a runtime property of the
object or reference. An object is said to have non-vacuous
initialization if it is of a class or aggregate type and it or one of
its subobjects is initialized by a constructor other than a trivial
default constructor. The lifetime of an object of type T begins when:
- storage with the proper alignment and size for type T is obtained, and
- if the object has non-vacuous initialization, its initialization is complete,
...
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:
- the glvalue is used to access the object, or
- ...
第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++ 魔法来拖拽构造函数参数,直到 class 在函数退出时知道要构造的参数。
我认为你写的代码是安全的。
要创建派生类型的工厂,因为您不能 重载 return 类型,
为什么不只是有不同的命名方法?调用者必须知道它想要什么类型才能声明变量,因此要求他们也知道要调用什么工厂方法似乎并不太麻烦。例如:
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>();
如果每个派生 class 调用构造函数之前所需的设置代码不同,或者如果每个派生 class 的构造函数采用不同的参数,则可以使用模板特化来解决这个问题.
但是请注意,您仍然需要在调用工厂时提供您正在创建的类型作为模板参数。据我所知,没有办法解决这个问题。
我有一个 classes 的多态层次结构。虽然我也支持标准工厂方法,我只使用基 class 指针,但我还想要一个工厂机制,它给我派生 classes,这并不容易,因为这些函数仅在它们的 return 类型。这就是为什么我想出重载函数并让编译器选择正确的想法的原因。
一个简单的应用是我可以编写创建派生对象的函数,"prepare" 它和 return 指向它的基指针以便在不需要类型信息时进一步访问没有了。
- 问题一:下面的可以吗?
- 问题2:newObject()的参数只是为了让编译器选择正确的函数。我担心在声明它的同一行上使用
p1
。在newObject()
中设置它之前,我从未读取过它的值,但我不确定它是否会导致未定义的行为。我还有一个自我赋值,因为 returned 值被赋给了它自己...
这是代码示例:
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;
}
这是一个有问题的设计,但从纯语言的角度来看是正确的。 C++ 标准是这样说的:
1 The lifetime of an object or reference is a runtime property of the object or reference. An object is said to have non-vacuous initialization if it is of a class or aggregate type and it or one of its subobjects is initialized by a constructor other than a trivial default constructor. The lifetime of an object of type T begins when:
- storage with the proper alignment and size for type T is obtained, and
- if the object has non-vacuous initialization, its initialization is complete,
...
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:
- the glvalue is used to access the object, or
- ...
第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++ 魔法来拖拽构造函数参数,直到 class 在函数退出时知道要构造的参数。
我认为你写的代码是安全的。
要创建派生类型的工厂,因为您不能 重载 return 类型, 为什么不只是有不同的命名方法?调用者必须知道它想要什么类型才能声明变量,因此要求他们也知道要调用什么工厂方法似乎并不太麻烦。例如:
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>();
如果每个派生 class 调用构造函数之前所需的设置代码不同,或者如果每个派生 class 的构造函数采用不同的参数,则可以使用模板特化来解决这个问题.
但是请注意,您仍然需要在调用工厂时提供您正在创建的类型作为模板参数。据我所知,没有办法解决这个问题。