C++ class declaration

I often gets confused on this as I am reading some of the libraries that I am using in my code and how they are being used.

Some classes are declared like Option 1 below but some are defined like Option 2.
I cannot exactly understood when do you choose one over the other?

Option 3 is a little bit different as I have used new operator to create an object in the Heap so it returns to me a pointer to that object.

Could somebody please help point me to the key concept maybe that I am missing. Thank you.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// example: class constructor
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle (int,int);
    int area () {return (width*height);}
};

Rectangle::Rectangle (int a, int b) {
  width = a;
  height = b;
}

int main () {
  // Option 1
  Rectangle rect (3,4);
  Rectangle rectb (5,6);
  cout << "rect area: " << rect.area() << endl;
  cout << "rectb area: " << rectb.area() << endl;
  cout << "*****************************"<< endl;
  
  // Option 2
  Rectangle rect2 = Rectangle(3,4);
  Rectangle rectb2 = Rectangle (5,6);
  cout << "rect2 area: " << rect2.area() << endl;
  cout << "rectb2 area: " << rectb2.area() << endl;
  cout << "*****************************"<< endl;
  
  // Option 3
  Rectangle *rect3 = new Rectangle(3,4);
  Rectangle *rectb3 = new Rectangle (5,6);
  cout << "rect3 area: " << rect3->area() << endl;
  cout << "rectb3 area: " << rectb3->area() << endl;
  delete rect3;
  delete rectb3 ;
  
  return 0;
}
Last edited on
you can do all this with an integer, too :)

int x = 3;
int x;
int x(5);
int x{42};
int x = y; //assume variable y exists.
and so on.

line 19 and 26 are virtually identical and a smart compiler will do the same thing with both. Prefer 19, as it is shorter and simpler. Technically, 26, a dumb compiler would create a rectangle object with no name and copy it into rect2 variable with the assignment operator on 26, but I don't think anything is that dumb anymore, or use the copy constructor, if one was provided. If the copy constructor was provided, I think it HAS to use it there, because it could do something special. If it does do something special and you need that, then you have to do it the line 26 way, but that smacks of odd and difficult to follow/use code. Someone can correct me if I am wrong here -- I have not done the line 26 way in a very long time.

option 3 is dynamic memory. Prefer to NOT use this at all. If you use it, you need to use the pet rock programming style: pick up your pet rock and pet it, then explain to it with technical detail WHY you are compelled to do that for your particular situation instead of using a C++ container that manages dynamic memory for you. There are times you will need it, but if you can't justify it, then you probably do not. "My teacher said to use it" works for justification for now, to learn the mechanics behind it. Pointers are a powerful tool, but easy to misuse. If you do use them, insist on using the so called 'smart pointer' objects provided by c++, for example the unique_ptr type.

however, lets look again at line 33 in option 3: see that new statement? It looks a LOT like line 26! The new keyword is handling getting the pointer and the dynamic memory part of the code, but the rectangle object is just a normal rectangle object.
Last edited on
Also int x = int(42); :)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
using namespace std;

class Rectangle {
	int width, height;
public:
	Rectangle(int, int);
	int area() {
		return (width * height);
	}
};

Rectangle::Rectangle(int a, int b) {
	width = a;
	height = b;
}

int main() {
  // Option 1
	Rectangle rect { 3, 4 };
	Rectangle rectb { 5, 6 };

	cout << "rect area: " << rect.area() << endl;
	cout << "rectb area: " << rectb.area() << endl;
	cout << "*****************************" << endl;

	// Option 2
	auto rect2 { Rectangle{3, 4} };
	auto rectb2 { Rectangle{5, 6} };

	cout << "rect2 area: " << rect2.area() << endl;
	cout << "rectb2 area: " << rectb2.area() << endl;
	cout << "*****************************" << endl;
}


With modern C++, then my Option 1 above using universal initialisation. Although using AAA (Almost Always Auto), then option 2

https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/
Hello. This is an interesting conversation. There are many ways to use classes. Some are simple with a clean semantic, but others seem really hard. I am not a big fan of auto methods. It seems to me that it does the work (moreover efficiently), despite an unclear understanding about what happens exactly in the process. It's like a catchall - I don't know what I am doing here - so I use auto. I guess that some dev preferences interact in the choice - according to pro and cons and the goal itself ++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
using namespace std;
// class
class Cube {
    float width;
    float height;
    float length;

public:
    Cube(float, float, float); // declaration
    float volume() { return (width * height * length); }
};
// definition
Cube::Cube(float w, float h, float l) {
    width = w;
    height = h;
    length = l;
}

int main() 
{   // option 1
    Cube c1(5, 4, 3.5); // Cube c1{ 5, 4, 3.5 };
    cout << "Cube option 1 volume : " << c1.volume() << endl;
    // option 2
    Cube c2 = Cube(5, 4, 3.5); // Cube c2 = Cube{ 5, 4, 3.5 };
    cout << "Cube option 2 volume : " << c2.volume() << endl;
    // option 3
    auto c3(Cube(5, 4, 3.5)); // auto c3{ Cube{ 5, 4, 3.5 } };
    cout << "Cube option 3 volume : " << c3.volume() << endl;
    // option 4
    Cube* c4 = new Cube(5, 4, 3.5); // Cube* c4 = new Cube{ 5, 4, 3.5 };
    cout << "Cube option 4 volume : " << c4->volume() << endl;
    delete c4;
    // option 5
    unique_ptr<Cube> c5 = make_unique<Cube>(5, 4, 3.5);
    cout << "Cube option 5 volume : " << c5->volume() << endl;
    c5.reset(nullptr);
    // option 6
    auto c6 = make_unique<Cube>(5, 4, 3.5);
    cout << "Cube option 6 volume : " << c6->volume() << endl;
    c6.reset(nullptr);

    return 0;
}
Last edited on
Thank you all for such detailed explanations!
jonnin wrote:
If the copy constructor was provided, I think it HAS to use it there, because it could do something special. If it does do something special and you need that, then you have to do it the line 26 way, but that smacks of odd and difficult to follow/use code.

Lets test that:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <iostream>
using std::cout;
using std::endl;
// class
class Cube {
    float width;
    float height;
    float length;

public:
    Cube(float, float, float); // declaration
    Cube(const Cube&);
    Cube(Cube&&);
    float volume() { return (width * height * length); }
};
// definition
Cube::Cube(float w, float h, float l)
 :  width  {w},
    height {h},
    length {l}
{
    cout << "Custom ctor\n";
}

Cube::Cube(const Cube& rhs)
 :  width  {rhs.width},
    height {rhs.height},
    length {rhs.length}
{
    cout << "Copy ctor\n";
}

Cube::Cube(Cube&& rhs)
 :  width  {rhs.width},
    height {rhs.height},
    length {rhs.length}
{
    rhs.width = 0;
    rhs.height = 0;
    rhs.length = 0;
    cout << "Move ctor\n";
}

int main() 
{   // option 1
    Cube c1(5, 4, 3.5); // Cube c1{ 5, 4, 3.5 };
    cout << "Cube option 1 volume : " << c1.volume() << endl;
    // option 2
    Cube c2 = Cube(5, 4, 3.5); // Cube c2 = Cube{ 5, 4, 3.5 };
    cout << "Cube option 2 volume : " << c2.volume() << endl;
    // option 3
    Cube c3(Cube(5, 4, 3.5)); // auto c3{ Cube{ 5, 4, 3.5 } };
    cout << "Cube option 3 volume : " << c3.volume() << endl;
    // option 4
    Cube c4 = std::move( Cube(5, 4, 3.5) );
    cout << "Cube option 4 volume : " << c4.volume() << endl;
}

Custom ctor
Cube option 1 volume : 70
Custom ctor
Cube option 2 volume : 70
Custom ctor
Cube option 3 volume : 70
Custom ctor
Move ctor
Cube option 4 volume : 70

This was with cpp.sh's compiler (C++14 mode, -O0).

So no, the copy constructor was not called. The move constructor is, but only with the std::move incentive.
Last edited on
@keskiverto Thank you for this clean example. Just a question. What is the siginification of rhs? Often I saw these letters in some codes. Thanks ++
x = y;
x is on the left, in english, its the Left Hand Side (lhs)
y is on the right, the Right Hand Side (rhs).
Registered users can post here. Sign in or register to post.