9

I'd like to keep MyClass in the stack memory (simpler, faster) in the following code, but avoid calling the default constructor:

#include <iostream>

class MyClass {
public:
  MyClass() {
    std::cout << "MyClass()" << std::endl;
  }

  MyClass(int a) {
    std::cout << "MyClass(" << a << ")" << std::endl;
  }

  MyClass(const std::string& a) {
    std::cout << "MyClass(\"" << a << "\")" << std::endl;
  }

  void doStuff() {
    std::cout << "doStuff()" << std::endl;
  }
};

int main(int argc, char* argv[]) {
  bool something;
  if (argc > 1)
    something = 1;
  else
    something = 0;

  MyClass c;
  if (something)
    c = MyClass(1);
  else
    c = MyClass("string");
  c.doStuff();

  return 0;
}

As far as I know, the only way to avoid calling the default constructor would be to use a pointer, but then I would have to allocate in the heap and deal with memory management. Is there any other way?

egoard
  • 595
  • 5
  • 8
  • 1
    maybe you could subclass and override the default constructor and have it be a no-op? – nielsbot Aug 20 '13 at 23:13
  • 1
    For this particular case the ternary operator can be handy. – Benjamin Bannier Aug 20 '13 at 23:13
  • 1
    @Benjamin: The ternary operator won't work because the results are two different types. – Cameron Aug 20 '13 at 23:14
  • Placement new doesn't do it for you, correct? http://www.drdobbs.com/cpp/calling-constructors-with-placement-new/232901023 – Stu Aug 20 '13 at 23:16
  • Maybe some hack using helper function that returns reference to either one or another object and reference to const to keep the object alive, and some const_cast to use non-const member function... – Dialecticus Aug 20 '13 at 23:17
  • 1
    @Cameron: You are right. Is copy-ellison guaranteed for `auto a = something ? MyClass(1) : MyClass("string")` or is that just my compiler? – Benjamin Bannier Aug 20 '13 at 23:19
  • What exactly are you trying to do / avoid? Just use a unique_ptr (or auto_ptr if pre-C++11 and be done with it). – Brian Neal Aug 20 '13 at 23:21
  • @BenjaminBannier I doubt copy elision is *ever* guaranteed by the standard, but this case is so simple, even a simplistic copy elision pass should pick it up. –  Aug 20 '13 at 23:22
  • @Cameron The results are *what* two different types? I can only see MyClass in the question. – user207421 Aug 20 '13 at 23:24
  • 2
    Why is it a really a problem (assuming you don't actually have a `cout << ... ` in your default constructor. If it's EMPTY, even if it's called, it's a few clock-cycles, so unless you do this millions of times, you are not going to notice - it's surprising how many times you can call an empty function in a second. – Mats Petersson Aug 20 '13 at 23:24
  • @EJP: Sorry, I wasn't clear. I thought Benjamin was thinking of `MyClass c(something ? 1 : "string")` which of course incorrectly mixes the `int` and `const char*` types. But that's not what he meant! (Or, maybe it was, on second thought -- egoard is trying to avoid constructor calls, after all.) – Cameron Aug 20 '13 at 23:28
  • @Benjamin: That's probably the cleanest way, but it seems [copy-/move-elision is not guaranteed](http://stackoverflow.com/a/9267964/21475) (though I imagine it would be done with optimizations enabled, at least in a simple case such as this). – Cameron Aug 20 '13 at 23:33
  • @delnan: Sorry for being unclear. I meant if above code (even with the potential branch) would allow copy ellision as in the standard or if this was my compiler being smarter than permitted. – Benjamin Bannier Aug 20 '13 at 23:37
  • @BenjaminBannier - Your copy elision suggestion works even without C++11 turned on... see my answer posted below. I compiled that on GCC 4.7.2 without any flags specified at all. – phonetagger Aug 20 '13 at 23:45
  • A more interesting problem than the answere for this question is why you have this requirement? For what senario case? – ZijingWu Aug 21 '13 at 03:17

6 Answers6

14

If you don't mind using a total hack, you can try a placement new.

char mem[sizeof(MyClass)] alignas(MyClass);
auto custom_deleter = [](MyClass *p){ p->~MyClass(); };
std::shared_ptr<MyClass> c(new (mem) MyClass, custom_deleter);

You use the alignas to make sure the automatic memory allocated is properly aligned for your object. The custom_deleter function calls the destructor without freeing the memory, which is needed when using automatic memory with a smart pointer. A demo of the code can be found here.

But, for your problem a more elegant solution exists. You can use a copy constructor instead:

  MyClass c = something ? MyClass(1) : MyClass("string");
  c.doStuff();
jxh
  • 69,070
  • 8
  • 110
  • 193
5

You're right. It isn't possible for you do avoid calling the default constructor, unless you want to duplicate some code like shown below.

if (something) {
    MyClass c(1);
    c.doStuff();
}
else {
    MyClass c("string");
    c.doStuff();
}

I would recommend that you create the object of the heap, but delegate the memory handling to another class. With C++03, there is the std::auto_ptr class which can be used. With C++11, auto_ptr has be deprecated, and instead you can use shared_ptr or unique_ptr.

Here is some example code using shared_ptr -

std::shared_ptr<MyClass> c;
if (something)
    c.reset(new MyClass(1));
else 
    c.reset(new MyClass("string"));

c->doStuff();

The object will automatically be deleted when it goes out of scope.

In general, it is recommended to use smart pointers instead of doing the memory management yourself. This is especially useful when you're dealing with code that can throw exceptions.

Vishesh Handa
  • 1,830
  • 2
  • 14
  • 17
5

Benjamin Bannier's suggestion works for me on GCC 4.7.2 with no special compiler flags (i.e., default optimization), or with -O0, -O1, -O2, or -O3:

int main(int argc, char* argv[]) {
  bool something;
  if (argc > 1)
    something = 1;
  else
    something = 0;

  MyClass c = something ? MyClass(1) : MyClass("string");

  c.doStuff();

  return 0;
}

I get the same results when I try it on GCC 3.4.4 (circa 2004), GCC 3.4.6 (2006), GCC 4.2.4 (2007), and GCC 4.7.2 (2012).

phonetagger
  • 7,701
  • 3
  • 31
  • 55
2

Try, this might avoid extra copy due to compiler copy elision:

MyClass makeInstance(int a, string& b) {
    if (something) {
        return MyClass(a);
    } else {
        return MyClass(b);
    }
}

I tried it and in my case I see only one object constructed and destroyed.

Eugene
  • 9,242
  • 2
  • 30
  • 29
  • `something` is not in scope here. Also, this forces me to create always both an `int` and a `string`, even if I only want one form. This could be solved with an overloaded function, but then you clearly get into overengineering land when there are much simpler solutions available. – Benjamin Bannier Aug 21 '13 at 05:19
1

Modern approach

Since C++11 we have std::aligned_storage, which can be used to reserve stack space for an object in a type-safe way without invoking its constructor. See here: https://en.cppreference.com/w/cpp/types/aligned_storage.

I had a particular usage scenario for this, and simply post my solution. Should be fairly simple to replicate and customize for other needs:

#include <new>
#include <iostream>

template<typename T>
using AlignedStorage = std::aligned_storage<sizeof(T), alignof(T)>::type;

struct A
{
    A(int _x) : x(new int(_x)) {}
    ~A() { delete x; }
    int* x;
};

struct B
{
    AlignedStorage<A> a;
};

B* create_b()
{
    auto res = new B(); // the member a does not get constructed!

    new (&res->a) A(42); // does not allocate, but calls constructor!

    return res;
}

void destroy_b(B* b)
{
    (reinterpret_cast<A*>(&b->a))->~A(); // destruct, but not deallocate!

    delete b; // be can safely be deallocated
}

int main()
{
    B* b = create_b();
    std::cout << "b->a = " << *(reinterpret_cast<A*>(&b->a)->x) << '\n';
    destroy_b(b);

    return 0;
}

Remarks

For the example above, the struct B gets allocated on the heap, but it contains a struct A which is stored as a value. The code was compiled with g++-11 -std=c++20 example.cpp, which means that it requires C++20. For C++11 I get some compiler errors, so likely other stuff has to be added for that to work.

alexpanter
  • 1,222
  • 10
  • 25
0

There is only one good solution to this that avoids any unnecessary constructor calls: lazy initialization. Have only one constructor, that simply gets your object into a defined state, and do the actual initialization in an init() method. Like this:

class MyClass {
public:
    MyClass() : initialized(false) { std::cout << "MyClass()" << std::endl; };

    void init(int a) {
        std::cout << "MyClass(" << a << ")" << std::endl;
        initialized = true;
    };

    void init(const std::string& a) {
        std::cout << "MyClass(\"" << a << "\")" << std::endl;
        initialized = true;
    };

    void doStuff() {
        assert(initialized);
        std::cout << "doStuff()" << std::endl;
    };

private:
    bool initialized;
};

Then you can easily do the following, initializing you object exactly once without using any kind of hack:

  MyClass c;
  if (something) {
      c.init(1);
  } else {
      c.init("string");
  }
  c.doStuff();
cmaster - reinstate monica
  • 38,891
  • 9
  • 62
  • 106
  • The asker wanted to avoid calling the default constructor (perhaps unreasonably, but that was the requirement). But something like what you proposed can be adapted into a working solution by following the [builder pattern](http://en.wikipedia.org/wiki/Builder_pattern). Then, the initialization (ie, creation) of `c` can be deferred until the builder is initialized properly. Then `c` is initialized by the builder. – jxh Aug 21 '13 at 22:48
  • @jxh It's true that the OP asked how to avoid the default constructor, but from what I can see, doing so is not his problem; his problem seems to be the unnecessary initialization. And that is the problem I aimed to solve. – cmaster - reinstate monica Aug 22 '13 at 06:31