简体   繁体   中英

C++ avoid constructing an object

I have an class whose all default constructors are beyond my control. I need to avoid constructing them in some conditions and not in others. Is there any what I can do this?

The pattern is something like this:

class A
{
public: 
    //ctor + dtor + methods
    A(); 
    ~A();
    // Data members beyond my control
    X1 x1;
    X2 x2;
    ...
    XN xn;
};

class B
{
public:
    B()
    {}

    // Data members
    A a;   // This instance of A is the issue
};

The issue is X1 to XN are completely beyond my control. Class A is just a class which packages x1 to xn together. I want to conditionally create the instance of A , in B . Now the issue is that the default constructors of X1 to XN do something that needs to be avoid under some conditions and not others. So no matter if I construct A or not, B will try to create a using the default constructor of A , which will in turn initiate default constructors of X1 to XN . This is what I need to avoid.

I am using macros to fix this currently, but wanted to see if there is a better solution out there.

As Konrad Rudolph pointed out:

class B
{
public:
  B(bool create = false)
  {
    a = (create ? new A : NULL);
  }
  ~B()
  {
    delete a;
  }
private:
  A* a;
};

You may want to use a Nullable type like boost::optional. This would look like:

class B {
public:
    B()
    {
        /*
         * Here, a has been default constructed and is empty
         * You can do computations here and then...
         */
        if(/* some elaborate condition*/) {
            a = A();
            /* access *a from here on */
        } else {
            /* anything you want */
        }
    }

private:
    boost::optional<A> a;
};

This answers the question but I think a more appropriate answer could be given if you told what you really wanted to achieve. I feel this is more of a design issue rather than a language issue. Extended thoughts follow.

In the above 'solution', what goes in the else clause? Since A can apparently only be default-constructed, it's not like you can put a different constructor call. If you do not however initialize a you introduce (cyclomatic) complexity since that would mean that every method will have to check if a is active. Alternatively you could throw; I would however refactor the code that do the checking (or whatever) in a function; either a private static method or an anonymous/static freestanding function like this:

namespace {
A
prepare_A()
{
    /* elaborate computations, possibly throw */
    return A();
    /*
     * if A had different constructors we could also conditionally
     * return an A(0) or whatever.
     */
}
} // namespace

B::B()
:
    a(prepare_A())
{
    /*
     * a is of type A, not boost::optional<A>
     */
}

This however assumes that A is copyable. If that's not the case or it's not acceptable to copy A around, I'd consider the first solution acceptable on the condition that as part of the class invariant a is never empty.

It'd be easier to recommend something if we knew the relationship between A and B . Why would you put an A member in B if you want to conditionally initialize it? Perhaps the preferred relationship between the two should not be aggregation but association: a pointer member (or reference member if you're careful about the assignment operator) to A such as:

class B {
public:
    explicit
    B(A& a_)
    /*
     * Important: not A const&,
     * we only want lvalues.
     * Contract on the caller: a must remain valid during
     * the lifetime of *this.
     */
    :
        a(&a_)
    {
        /*
         * class invariants: a != 0,
         * *a remains valid for the lifetime of B
         */
    }

private:
    A* a;
};

That way you introduce no cyclomatic complexity and you don't care how and when an A is constructed. The contract adds a bit of a burden on the caller but since we can only pass lvalues to the constructor it's hard(er) to misuse.

It's unclear which constructor you want to avoid but in either case you can use a union.

From Stroustrup 4th ed:

If a union has a member with a user-defined constructor, a copy operation, a move operation, or a destructor, then that special function is deleted (§3.3.4, §17.6.4) for that union; that is, it cannot be used for an object of the union type.

So if you want to not construct A when it is in B, use:

class B {
public:
    B()
    {}

    // Data members
    union {
       A a;   // This instance of A is the issue
    };
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM