89

Is it possible to declare a variable in c++ without instantiating it? I want to do something like this:

Animal a;
if( happyDay() ) 
    a( "puppies" ); //constructor call
else
    a( "toads" );

Basially, I just want to declare a outside of the conditional so it gets the right scope.

Is there any way to do this without using pointers and allocating a on the heap? Maybe something clever with references?

asheeshr
  • 4,088
  • 6
  • 31
  • 50
Quantum7
  • 3,165
  • 3
  • 34
  • 45

11 Answers11

57

You can't use references here, since as soon as you'd get out of the scope, the reference would point to a object that would be deleted.

Really, you have two choices here:

1- Go with pointers:

Animal* a;
if( happyDay() ) 
    a = new Animal( "puppies" ); //constructor call
else
    a = new Animal( "toads" );

// ...
delete a;

or with a smart pointer

#include <memory>

std::unique_ptr<Animal> a;
if( happyDay() ) 
    a = std::make_unique<Animal>( "puppies" );
else
    a = std::make_unique<Animal>( "toads" );

2- Add an Init method to Animal:

class Animal 
{
public:
    Animal(){}
    void Init( const std::string& type )
    {
        m_type = type;
    }
private:
    std:string m_type;
};

Animal a;
if( happyDay() ) 
    a.Init( "puppies" );
else
    a.Init( "toads" );

I'd personally go with option 2.

dwb
  • 2,136
  • 13
  • 27
joce
  • 9,624
  • 19
  • 56
  • 74
48

You can't declare a variable without calling a constructor. However, in your example you could do the following:

Animal a(happyDay() ? "puppies" : "toads");
Greg Hewgill
  • 951,095
  • 183
  • 1,149
  • 1,285
41

You can't do this directly in C++ since the object is constructed when you define it with the default constructor.

You could, however, run a parameterized constructor to begin with:

Animal a(getAppropriateString());

Or you could actually use something like the ?: operator to determine the correct string. (Update: @Greg gave the syntax for this. See that answer)

iammilind
  • 68,093
  • 33
  • 169
  • 336
Uri
  • 88,451
  • 51
  • 221
  • 321
  • 2
    +1. This is the general form of the solution -- wrap it inside a function. (As you say, ?: often does the job and is more convenient when it does, but writing a separate function will *always* work.) – j_random_hacker Apr 29 '09 at 03:55
  • However, if your constructor needs to take multiple arguments, do you make multiple functions, one for each argument? – newacct Apr 29 '09 at 06:22
  • There are some studies that show that it is better to not have constructors with multiple arguments but rather to create with default and then use setters. That being said, yes, you'd do a function per argument, or even better, have an interim struct to represent the cohesive elements that constitute the parameters, if they are related. – Uri Apr 29 '09 at 07:09
18

I prefer Greg's answer, but you could also do this:

char *AnimalType;
if( happyDay() ) 
    AnimalType = "puppies";
else
    AnimalType = "toads";
Animal a(AnimalType);

I suggest this because I've worked places where the conditional operator was forbidden. (Sigh!) Also, this can be expanded beyond two alternatives very easily.

Steve Fallows
  • 6,274
  • 5
  • 47
  • 67
8

If you want to avoid manually managing memory, you could use a smart pointer:

std::unique_ptr<Animal> p_a;
if ( happyDay() )
    p_a = std::make_unique<Animal>( "puppies" );
else
    p_a = std::make_unique<Animal>( "toads" );

// do stuff with p_a-> whatever.  When p_a goes out of scope, it's deleted.

If you still want to use the . syntax instead of ->, you can do this after the code above:

Animal& a = *p_a;

// do stuff with a. whatever
Jan Schultke
  • 17,446
  • 6
  • 47
  • 96
paquetp
  • 1,639
  • 11
  • 19
  • This needs to be changed to auto_ptr p_a(new Animal); otherwise the auto_ptr just has a null pointer. Though I like the second idea as it doesn't copy it - but you do have to be mindful that the life is in that scope. – Natalie Adams Sep 16 '13 at 00:26
  • 3
    @NathanAdams, an auto_ptr initialized with null is fine here, it will be either "puppies" or "toads" later. Having an extra "new Animal" is redundant. – fxam Sep 24 '13 at 16:03
7

Since c++17, there is now an overhead-free way to do this: std::optional. The code in this case would be:

#include <optional>

std::optional<Animal> a;
if (happyDay()) {
    a.emplace("puppies");
} else {
    a.emplace("toads");
}
Chronial
  • 66,706
  • 14
  • 93
  • 99
  • This could be a good solution. It's not quite the intended use of optional (marking values that may or may not be initialized) but it does avoid calling `a`'s default constructor. – Quantum7 Jun 09 '20 at 08:20
  • Note that `optional` does have a *tiny* amount of overhead for storing the bool (8 bytes on my system) and initializing itself. – Quantum7 Jun 09 '20 at 08:24
  • Gist exploring this (and other) solutions: https://gist.github.com/sbliven/359d180753febc4777ac79bb97685b5b – Quantum7 Jun 09 '20 at 08:52
  • 1
    @Quantum7 a good compiler will completely optimize away the optional: https://godbolt.org/z/x9gncT – Chronial Jun 28 '20 at 20:20
7

In addition to Greg Hewgill's answer, there are a few other options:

Lift out the main body of the code into a function:

void body(Animal & a) {
    ...
}

if( happyDay() ) {
  Animal a("puppies");
  body( a );
} else {
  Animal a("toad");
  body( a );
}

(Ab)Use placement new:

struct AnimalDtor {
   void *m_a;
   AnimalDtor(void *a) : m_a(a) {}
   ~AnimalDtor() { static_cast<Animal*>(m_a)->~Animal(); }
};

char animal_buf[sizeof(Animal)]; // still stack allocated

if( happyDay() )
  new (animal_buf) Animal("puppies");
else
  new (animal_buf) Animal("toad");

AnimalDtor dtor(animal_buf); // make sure the dtor still gets called

Animal & a(*static_cast<Animal*>(static_cast<void*>(animal_buf));
... // carry on
Logan Capaldo
  • 39,555
  • 5
  • 63
  • 78
  • Do you know if there's a way to make the placement new version guarantee correct alignment (Pre c++11)? – enobayram Dec 09 '12 at 23:52
3

The best work around is to use pointer.

Animal a*;
if( happyDay() ) 
    a = new Animal( "puppies" ); //constructor call
else
    a = new Animal( "toads" );
rkadeFR
  • 39
  • 1
1

There is a way to do this without pointers/heap memory, this syntax is just a bit gibberish. Here is an example using std::string. I don't recommend doing this unless you really need the performance.

uint8_t object[sizeof(std::string)];

int main() {
    if(true)
        new(&object) std::string("Your arguments");
    else
        new(&object) std::string("Your other arguments");

    (*(std::string*)(&object)).append("");
    std::cout << (*(std::string*)(&object));
    return 0;
}

The annoying part about this is you have to cast object to a string every time you want to use it:

(*(std::string*)(&object))
patrick
  • 11
  • 1
0

You can also use std::move:

class Ball {
private:
        // This is initialized, but not as needed
        sf::Sprite ball;
public:
        Ball() {
                texture.loadFromFile("ball.png");
                // This is a local object, not the same as the class member.
                sf::Sprite ball2(texture);
                // move it
                this->ball=std::move(ball2);
        }
...
RodolfoAP
  • 743
  • 1
  • 7
  • 18
  • `std::move` definitely has potential for use here, but this code doesn't show how to get around the double initialization in the question. Could you rewrite your answer to follow my question more closely (e.g. implementing an `Animal(char *)` constructor)? You should also state that this requires C++11. – Quantum7 Apr 07 '20 at 13:57
-6

Yes, you can do do the following:

Animal a;
if( happyDay() )
    a = Animal( "puppies" );
else
    a = Animal( "toads" );

That will call the constructors properly.

EDIT: Forgot one thing... When declaring a, you'll have to call a constructor still, whether it be a constructor that does nothing, or still initializes the values to whatever. This method therefore creates two objects, one at initialization and the one inside the if statement.

A better way would be to create an init() function of the class, such as:

Animal a;
if( happyDay() )
    a.init( "puppies" );
else
    a.init( "toads" );

This way would be more efficient.

DeadHead
  • 2,251
  • 16
  • 16
  • 2
    Are you sure about this? I think this will invoke default constructor and then an assignment operator, so you'll lose the old a. – Uri Apr 29 '09 at 00:24
  • Yeah, I forgot about the initial constructor at first. That's why I usually test my code before posting it... didn't this time... – DeadHead Apr 29 '09 at 00:27
  • 3
    Yea, but that assumes that (1) Animal has an accessible default constructor (it may not make sense to have a default constructor in some classes), (2) Animal has an assignment operator (some classes can't be assigned by design), and (3) constructing and assigning Animal has the same effect as constructing it directly. – newacct Apr 29 '09 at 00:27
  • 3
    Using an init() method is probably a bad idea as that implies the object is not valid after the constructor is finished. – Martin York Apr 29 '09 at 00:29
  • @Martin, I don't see what's wrong with that... as it is in the OP's example, the object shouldn't be used until a value is set to it, through whatever method he is going to use. – DeadHead Apr 29 '09 at 00:36
  • 1
    @DeadHead: Careful with your syntax. Your Animal a(); line is a function prototype, not a variable declaration. Animal a; is what you mean (default constructor is still invoked). – Drew Hall Apr 29 '09 at 01:50
  • -1: I've never seen a compiler that allows this. Even if it worked the way the answerer said it does, that's still a bad idea because you're constructing more objects than you need to. – ArtOfWarfare Feb 06 '14 at 04:56