6

I'm a Java developer trying to pick up C++. Is it okay to use a setter inside a constructor in order to reuse the sanity checks the setter provides?

For example:

#include <stdexcept>
using namespace std;

class Test {
    private:
        int foo;
        void setFoo(int foo) {
            if (foo < 42) {
                throw invalid_argument{"Foo < 42."};
            }

            this->foo = foo;
        }

    public:
        Test(int foo) {
            setFoo(foo);
        };
};
Meme Master
  • 147
  • 1
  • 2
  • 6
  • It is valid. Note that `unsigned` types exist to get rid of the test here. – Jarod42 Jul 13 '16 at 08:48
  • If you don't want users of the class to not pass negative numbers, why not use `unsigned` instead? Then the compiler will handle the check at the time of compilation for you, instead of you needing a runtime check. – Some programmer dude Jul 13 '16 at 08:48
  • 2
    The `unsigned` stuff is not undisputed; see e.g. https://channel9.msdn.com/Events/GoingNative/2013/Interactive-Panel-Ask-Us-Anything 9:50, 42:40, 1:02:50 (Panel in which several prominent members of the committee argue against using unsigned integers for stuff other than bit-fiddling.) – Baum mit Augen Jul 13 '16 at 08:49
  • I used that just as an example, the test could be any arbitrary check, like if (foo < 42). – Meme Master Jul 13 '16 at 08:50
  • 1
    Off-top: is it normal to throw exceptions in constructor? – ilotXXI Jul 13 '16 at 08:54
  • 1
    @ilotXXI Yes. Exceptions are a way to guarantee a class invariant if some constructor input wouldn't satisfy it. – eerorika Jul 13 '16 at 08:57

6 Answers6

7

Yes, it is recommended to do this, basically for the reason you already mentioned.

On the other hand you should ask yourself if you need the setter at all and not directly implement the checks inside the constructor. The reason I am writing this is that setters in general result in mutable state which has many disadvantages as opposed to immutable classes. However sometimes they are required.

Another recommendation: If your class variable is an object and you can modify the constructor of this object, you could put the check into the constructor of this object:

class MyFoo {
public:
    MyFoo(int value) {
        if (value < 42) {
            throw invalid_argument{"Foo < 42."};
        }
        v = value;
    }
private:
    int v;
}

This will enable you to use an initialization list in the constructor of your Test class:

Test(int foo) : foo(foo) {}

However, now the check is a property of the class of the variable and no longer one of the owning class.

Frank Puffer
  • 8,135
  • 2
  • 20
  • 45
3

Yes you can. It's fine as long as your setters are not virtual, because it's inheritance hierarchy in calling right functions as the "this" ptr is not ready yet.

Here is Herb Sutter GOTW on this matter: http://www.gotw.ca/gotw/066.htm

paweldac
  • 1,144
  • 6
  • 11
  • @user2079303 I agree it's fine as long as dev is aware which function will be called in such situation. I've never thought about errors coming from calling non-virtual functions calling virtual onces. Good point. – paweldac Jul 13 '16 at 08:57
  • @user2079303: _"What you absolutely musn't do is call a member function that in turn calls a virtual member"_ Why not? How's that different from what you just said is safe? – Lightness Races in Orbit Jul 13 '16 at 09:14
  • @user2079303: 'Fraid so. Non-constructors do dynamic dispatch, and constructors do dynamic dispatch. The result is deterministic. It might not be what you expected if you're in the middle of constructing a more-derived object, but [it's "safe" and assuredly has the same effect in both cases](http://coliru.stacked-crooked.com/a/9a0f6498a5df9acb). – Lightness Races in Orbit Jul 13 '16 at 10:20
  • Modified example showing that `bar()`'s virtual dispatch will "know about" the most derived class after construction is complete: http://coliru.stacked-crooked.com/a/a21681a2e2c87e78 – Lightness Races in Orbit Jul 13 '16 at 10:27
  • @LightnessRacesinOrbit nice, thanks for setting my misinformation straight. I shall remove their mention so that other's won't be corrupted. – eerorika Jul 13 '16 at 13:04
1

Yes, that's fine as long as it makes sense to have a setter for a particular member variable (have some logic that can't be checked by assignment only for example) . In this example, setFoo could've just taken an unsigned int and the caller would know not to pass negative values. Which in turn could eliminate the check and thus the need for a setter. For more elaborate checks, a setter and usage of that setter in the constructor is just fine.

Hatted Rooster
  • 35,759
  • 6
  • 62
  • 122
1

Short answer: Yes. In fact, your example works.

Long answer: But it is not a good practice. Al least, you have to take care.

In general, a set function works with a constructed object. It is supposed that the invariant of the class holds. The functions in a class are implemented considering the invariant is true.

If you want other functions to be used in a constructor, you would have to write some code. For example, to create an empty object.

For example, if in your class you change setFoo in the future (let's say setFoo changes the member foo only it is larger) you example stop working.

EFenix
  • 831
  • 4
  • 11
0

This is okay.

The only situation you cannot call member function is when the base classes are not constructed yet.

can member functions be used to initialize member variables in an initialization list?

Community
  • 1
  • 1
jdh8
  • 3,030
  • 1
  • 21
  • 18
0

I know this doesn't fit your situation. Its just for the sake of completeness:

When you are simply settings member values (without checks like yours in setFoo) it is recommended to use initialization lists in the constructor. This prevents members being "initialized" 2 times: 1. with their default value, 2. with the value that you passed into the constructor.

class Test {
private:
    int foo_;

public:
    Test(int foo)
      : foo_(foo)
    { };
};
Wum
  • 306
  • 1
  • 10