我可以在C ++的这个工厂中声明的同一行上使用变量吗?

时间:2017-05-10 09:02:10

标签: c++ factory

我有一个类的多态层次结构。虽然我也支持标准的工厂方法,我只使用基类指针,但我也想要一个工厂机制,它给我派生类,这并不容易,因为这些函数的返回类型不同。这就是为什么我想出了重载函数的想法,让编译器选择正确的函数。

这个的简单应用是我可以编写创建派生对象的函数,"准备"它,并返回一个指向它的基指针,以便在不再需要类型信息时进一步访问。

  • 问题1:以下是否可以?
  • 问题2:newObject()的参数仅供编译器选择正确的函数。我担心在声明它的同一行使用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;
}

2 个答案:

答案 0 :(得分:4)

这是一个值得怀疑的设计,但从纯粹的语言角度来看是正确的。 C ++标准有这样说:

[basic.life]

  

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;
}

Live Example

上面的代码只是使用大量的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>();

如果在调用构造函数之前所需的设置代码对于每个派生类都不同,或者如果每个派生类的构造函数采用不同的参数,则可以使用模板特化来覆盖它。

但请注意,在调用工厂时,您仍需要提供作为模板参数创建的类型。据我所知,目前还没有办法解决这个问题。