2

Assume the following code:

class myClass{
    myClass(int a, int b, int c){};
};

main(){
   myClass cl(2,5,6);
}

myClass cl(2,5,6); will work. But what if I want the constructor to work only with specific values? For example a>1 b>2 c>1. Is there any way to detect wrong arguments and "cancel" the creation of cl within the constructor?

6 Answers6

5

Yes you can do that. You just have to validate the arguments inside constructor's body. If they are invalid then throw exception.

Class Invalid
{
 private:
    int m_x, m_y;
 public :
    class MyException : public exception {};

    Invalid ( int x, int y )
    {
       if ( x < 0 || y > 100 )
            throw MyException ();
       ...             
    }
}; 
ravi
  • 10,994
  • 1
  • 18
  • 36
  • Can you write an example? –  Nov 14 '14 at 15:56
  • @Dim_Ch If you don't know what exceptions are, continue reading about C++ and you will find out about it. – Some programmer dude Nov 14 '14 at 15:56
  • See also http://stackoverflow.com/questions/810839/throwing-exceptions-from-constructors – Mawg says reinstate Monica Nov 14 '14 at 15:59
  • @Dim_Ch just be warned that throwing an exception in a constructor is dangerous...the destructor will NOT be called for incomplete classes (aka your). So you'll either have to replicate your destructor in the constructor or figure something else out. – IdeaHat Nov 14 '14 at 16:10
  • +1 For exception using in constructor, but missing thrown exception handling in the main function ! – neo Nov 14 '14 at 16:13
  • Don't handle it! It's a programming error--the only safe thing to do is to crash. For the same reason, I don't get too worried about the fact that the dtor isn't called. IMO, of course. – dlf Nov 14 '14 at 16:13
  • @dlf I agree that pokemon exception handling is bad, but programming with the assumption that no user would ever handle an exception? What if this should be handled by a GUI? (dtor not being called can lead to memory leaks if you aren't following the rule of zero). this is one of the reasons why people tend to lean towards only throwing exception if the whole system cannot be recovered, and if that happens in your constructor than the constructor is probably doing too much...but then you are left with the problem that exceptions are the only way a constructor can fail! – IdeaHat Nov 14 '14 at 16:49
  • @IdeaHat Context if everything, of course, but in general I'd argue that if a constructor has documented that only certain inputs are valid and a programmer calls it with inputs that violate that, it's a fatal error. Now, if there's no way to determine whether the inputs are valid ahead of time it's a different story, but for the vanilla "val >= 0" type of constraint, I usually just write the equivalent of `if(!IsConstraintMet(val)) logCallStackAndCrash();`. – dlf Nov 14 '14 at 18:07
3

You can do something like this:

myClass(int a, int b, int c)
{
    if (a <= 1){
        throw something; // ToDo - define `something`, a text string would work.
    }
}

And so on. Note one important point, the destructor will not be called if an exception is thrown in a constructor (although any base class destructors will be called). That'a quite a common cause for memory leakage.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
1

Before I begin, I want to clarify that this is actually a pretty big topic in C++, and many design patterns are explicitly designed around this problem.

A nieve approach is to throw an exception in the contructor:

class myClass {
public:
  myClass(int a, int b, int c) 
  {
    if (a<=1 || b <= 2 || c<=1) throw "some exception";
  }
};

This is generally considered a bad practice, as the destructor for the class will never be called! As a rule of thumb, constructors should be fast and simple. If a constructor can fail, you should try something else. Also, exception handling is notoriously slow in C++.

So alot of people go with the an initialize call instead:

class myClass {
  public:
     myClass() { initialized_ = true;}
     void initialize((int a, int b, int c) { initialized_ = !(a<=1 || b <= 2 || c<=1);}
     bool alive() {return intialized_;}
  private:
     bool initialized_;
 };

Then, when you use the class you can check after an initialization attempt if the object succeeds.

 myClass c;
 c.initialize(2,5,6);

I personally don't like this because you end up with zombie classes.

 myClass c;
 c.initialize(0,0,0);
 c.foo();//Legal, compiles, but is WRONG

This Zombie Class apposes the idea of RAII, and honestly I shouldn't have to do that check all the time.

My prefered way of dealing with this is factory methods.

 class myClass
 {
 public:
    static myClass* makeMyClass(int a, int b, int c)
    {
       myClass* ret = new myClass();
       ret->initialize(a,b,c);
       if (!ret->alive()) {delete ret; return null;}
       return ret;
    }
 private:
    myClass() { initialized_ = true;}
     void initialize((int a, int b, int c) { initialized_ = !(a<=1 || b <= 2 || c<=1);}
     bool alive() {return intialized_;}
  private:
     bool initialized_;
 };

(protip don't use raw pointers, use smart pointers).

IdeaHat
  • 7,641
  • 1
  • 22
  • 53
0

You can use static_assert to achieve compilation time checking, but maybe you have to wrap your call in an ugly macro, or maybe in a template.

Something like (hopefully less ugly):

class myClass{
public:
  myClass(int a, int b, int c){};
};

#define SafeMyClass(obj, a,b,c) static_assert(a<b,"a<b"); static_assert(b<c,"b<c"); myClass obj(a,b,c);

int main(){
  SafeMyClass(cl,2,5,6);
  return 0;
}
dmoreno
  • 676
  • 6
  • 10
  • What about variables as parameter for the constructor? I don't think `static_assert` is a good idea for this scenario. – tgmath Nov 14 '14 at 16:02
  • The values are being passed into the constructor at run-time. A `static_assert` will not work here. – bfair Nov 14 '14 at 16:06
  • @dogjones it works as long as the parameters are `constexpr`, e.g. in the example. – tgmath Nov 14 '14 at 16:08
0

Since you know the range of acceptable values at program-writing time, an attempt to construct the class with incorrect values means your program has broken. You should use an assert. Asserts are used to document correct usage of a class / function / etc, and to ease the debugging process.
http://www.cplusplus.com/reference/cassert/assert/

class myClass{
    myClass(int a, int b, int c) {
        assert(a > 1 && b > 2 && c > 2); 
    };
};

assert will throw an exception if the boolean which you pass to it evaluates to false.

By saying assert(a > 1 && b > 2 && c > 2); you are saying "the program should never construct myClass with values for a, b, and c that are outside of the correct range". If the program does so, the program is incorrect. This will make it very easy for you to find and correct the error.

If the values are coming from somewhere that you can't control, such as user-input, you should validate that input outside of the myClass's constructor. This is proper separation of concerns.

Another advantage of using assert is that when you compile your release / optimized build, the asserts will evaluate to null statements. This way code which is intended to help you debug will not slow down your release build.

Remember to #include <assert.h>.

bfair
  • 1,101
  • 7
  • 16
0

This is how i do it

 class myClass{
    public:
        myClass(int a, int b, int c):aValue(a), bValue(b), cValue(c){};
    private:
        int aValue;
        int bValue;
        int cValue;
 };

 myClass::myClass(int a, int b, int c){
     if(a<2) throw rangeError("the 'a' should be larger than one");
     if(b<3) throw rangeError("the 'b' should be larger than one");
     if(c<2) throw rangeError("the 'c' should be larger than one");
 }

 void main(){
    try{
        myClass cl(2,5,6);
    }catch(rangeError re){
        cout << re.what() << endl;
    }
 }
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
user3802248
  • 39
  • 1
  • 1
  • 3