0

I touched C++ a few years ago and am having to go back to it for a project I'm working on. I learned most of the basics but never really nailed down on how C++ wants you to implement its idea of classes.

I have experience in other languages like Java, Python, and C#.

One of the troubles I'm having is understanding how to overload constructors in C++ and how to properly do inheritance.

For example, say I have a class called Rect and a class called Square that inherits from Rect.

In Java...

public class Rect {
   protected double m_length, m_width;

   public Rect(double length, double width) {
      this.m_length = length;
      this.m_width = width;
   }

   public double Area()
   {
      return (this.m_length * this.m_width);
   }

}

public class Square extends Rect {
   private String m_label, m_owner;
   public Square(double side) {
      super(side, side);
      this.m_owner = "me";
      this.m_label = "square";
   }

   //Make the default a unit square
   public Square() {
      this(1.0);
      this.m_label = "unit square";
   }
}

However the same process in C++ just feels convoluted.

Headers:

class Rect
{
public:
   Rect(double length, double width) : _length(length), _width(width) { } 
   double Area();

protected:
   double _length, _width;
}

class Square : public Rect 
{
public:
   Square(double side) : 
   Rect(side, side), _owner("me"), _label("square") { }

   Square() : Rect(1.0, 1.0), _owner("me"), _label("unit square");

private:
   std::string _owner, _label;
}

I feel like I shouldn't have to write out Rect again. It just seems like an awful lot of rewriting, especially since in my project I'm working with matrices a lot and I want to have constructors to be able to extend each other like in Java to avoid rewriting lots of code.

I'm sure there's a way to do this properly, but I haven't see anyone really talk about this problem.

EmDash
  • 59
  • 5
  • 1
    [The Definitive C++ Book Guide and List](https://stackoverflow.com/q/388242/5910058) – Jesper Juhl Jul 31 '19 at 17:30
  • It's mostly irrelevant to the question you're asking, but note that having a rectangle inherit from a square (or vice versa) is a pretty well known anti-pattern. The canonical example is circle vs. ellipse, but rectangle vs. square is essentially the same. https://en.wikipedia.org/wiki/Circle-ellipse_problem – Jerry Coffin Jul 31 '19 at 17:59
  • Oh yeah this is kinda a useless example. Square is basically the same as rect. I easily could've just made an overloaded constructor on rectangle but I wanted to demonstrate my problem without making good code lol. – EmDash Jul 31 '19 at 18:12

5 Answers5

3

If I have understood your question correctly then if the initializers of the strings are the same in the both constructors then you can use a delegate constructor

   explicit Square(double side) : 
   Rect(side, side), _owner("me"), _label("square") { }

   Square() : Square(1.0) {}

Also you can add default arguments as for example

   explicit Square(double side, const char *owner = "me", const char *label = "square" ) : 
   Rect(side, side), _owner(owner), _label(label ) { }

   Square() : Square(1.0, "me", "unit square" ) {}
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • WOAH. Default arguments? When was this a thing!? This looks like Python all of a sudden. Also the delegate constructor looks very elegant. Thank you! – EmDash Jul 31 '19 at 17:47
1

You have to spell out Rect because you could inherit from multiple classes.

You can simplify the two Square constructors to a single one:

class Square : public Rect 
{
public:
   Square(double side = 1.0) : 
   Rect(side, side), _owner("me"), _label("square") { }

  // ...

}

If you are serious about your initialization values, you could also do:

class Square : public Rect 
{
public:
   Square(double side = 1.0) : Rect(side, side) {}

private:
    std::string _owner = "me";
    std::string _label = "square";

}

The only remaining distinction is C++ constructor syntax vs. quasi-assignments in the C# CTor ("quasi" because assignments in CTors have somewhat different semantics than elsewhere)

That distinction has some merit: at the opening curly brace, all members and base classes are fully constructed. And that matters for two reasons:

First, C++ makes construction vs. assigmnent explicit. C# hides more aspects of "turning garbled memory into a valid object". (N.B. many of the merits of the performance benefits of that are probably in the scope of todays desktop compilers.)

Second, since there is deterministic construction / destruction in C++, order and dependency of initializations are important, e.g if there are dependencies between these elements, or if any of the initialization fails and throws an exception.

(N.B. the order of initialization depends on order of declaration, NOT the order of the CTor initialization.)

Give it a week, it will feel natural soon.

peterchen
  • 40,917
  • 20
  • 104
  • 186
0

Yes, in newer versions of C++ there is a way to remove a lot of the boilerplate. And even in older versions of C++, in the specific case you mention, there is an objectively much better way to handle the problem than what you're doing.

class Rect
{
public:
   Rect(double length, double width) : _length(length), _width(width) { } 
   double Area();

protected:
   double _length, _width;
}

class Square : public Rect 
{
public:
   explicit Square(double side = 1.0) : 
   Rect(side, side), _owner("me"), _label("square") { }
private:
   std::string _owner, _label;
}

For your case, default arguments are the way to go. And you should include the keyword explicit in order to avoid having the compiler automatically convert a double value into a Square because that would likely surprise people. Default arguments have existed in C++ for a very long time and should work on all compilers.

Another technique that helps with these kinds of issues that only works with post C++11 (which is almost all C++ compilers at this point) is delegating constructors.

A more limited technique that is also post C++11 is inheriting constructors.

A fourth technique that's sometimes applicable is to have default values for class members:

class Fribbler {
 public:
   fribble()  { i_have_been_fribbled_ = true; }
   is_fribbled() const { return i_have_been_fribbled_; }

 private:
   bool i_have_been_fribbled_ = false;
};

As a side note, you should not use names with a leading underscore (aka _) in C++. Those names are reserved for use by the library implementation or the compiler.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • Really? Should I stick with m_ for private members then? I always thought C++ uses just "_" to distinguish private members. – EmDash Jul 31 '19 at 17:51
  • @EmDash - Nope. An `m_` prefix is fine. Or an `_` suffix. I vastly prefer the latter as there's a lot less visual noise that way. But an `_` prefix should never be used for any variable anywhere, unless you're writing the compiler or the standard library. – Omnifarious Jul 31 '19 at 18:20
0
  1. Use Inheriting constructors to reuse the ctors from Rect (i.e. in your Square do using Rect::Rect;
  2. Use Delegating constructors to call one ctor from another.
  3. Side-note, Your base classes need to have virtual Destructors if you are planning on using them polymorphic-ally.
David
  • 602
  • 5
  • 14
0

From my understanding, constructor overloading in C++ is very similar to its function overloading, for this reason, overloaded constructors have the same name of the class but different number of arguments, and depending upon the number and type of arguments passed, specific constructor is called.

However, you can probably make them similar to this way

class Person
{
    int id;
    string name;

    public:
    //Empty Constructor
    Person(){
        this->id = 0;
        this->name = "default";
    }

    //Overloaded constructor with int parameter
    Person(int id){
        this->id = id;
    }

    //Overloaded constructor with a int parameter and a string
    Person(int id, string name){
        this->id =id;
        this->name = name;
    }

    void display(){
        cout << "Person Info: "<<" " << id << " " << name.c_str()<<"\n";
    }
};
  • Yes you COULD do this but this isn't truly initialization in C++. From what I've read this is merely assignment, which is not the way C++ prefers you do things. – EmDash Jul 31 '19 at 17:47