Can I use a variable on the same line it is declared in this factory in C++?

一曲冷凌霜 提交于 2019-12-24 00:55:03

问题


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.

  • 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 in newObject(), 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...

This is the code example:

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

EDIT:

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

回答1:


It's a questionable design, but correct from a pure language standpoint. The C++ standard has this to say:

[basic.life]

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
  • ...

Paragraph 7 explicitly specifies that using such a reference to assign into an object whose lifetime hasn't started yet is UB.

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.


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

Live Example

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.




回答2:


I think the code you've written is safe.

To create a factory of derived types, since you can't overload on return type, why not just have differently named methods? The caller already has to know what type it wants in order to declare the variable, so requiring them to also know what factory method to call doesn't seem too onerous. E.g.:

Derived1 * newDerived1() {
    return new Derived1();
}

Derived2 * newDerived2() {
    return new Derived2();
}

If you must have factory methods named the same, you could use a template:

template<
    typename T,
    typename = std::enable_if_t<std::is_base_of<Base, T>::value>
>
T* newObject() {
    return new T();
}

Derived1* d = newObject<Derived1>();

If the set-up code required before calling the constructor differs for each derived class, or if each derived class's constructor takes different arguments, you can use template specialisation to cover this.

Note, however, that you still need to provide the type you're creating as a template parameter when calling the factory. There's no way round this as far as I'm aware.



来源:https://stackoverflow.com/questions/43887931/can-i-use-a-variable-on-the-same-line-it-is-declared-in-this-factory-in-c

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!