avatar

目录
300 - cpp构造函数委托

C++构造函数委托

🧑‍💻 User

In this lecture we’re going to learn about constructor delegation, and this is a technique we havein C plus plus To have a constructor, use another constructor and avoid code duplication.Suppose we have two constructors for our square class here we have one that takes a single parameterand we have another that takes three parameters.We have modified the class now to have many member variables.We have the side, we have the color, we have an integer which is going to represent the shading ofthe square and we have another double member variable for the position of the square on the screen.For example, suppose we are doing some visual application so we can set up our constructors like thisand they are going to work.They are going to be two separate constructors.We can look at the implementations of this constructors.The one parameter constructor is going to only initialize the side parameter and the others are goingto be left uninitialized in the body here and it is possible to do this, but I don’t recommend it.But it is possible.So the second one is going to take three parameters.It’s going to have side parameter, the color and the shading.But notice that it is also leaving out the position and I have left this out on purpose because I havesomething to show you a little later in this lecture.So this constructor is also using our initializer list here.So side is going to be initialized with side param color is going to be initialized with color paramand shading is going to be initialized with shading parameter.This is going to work.But we are really duplicating work because we have two constructors really doing almost the same thing.It is possible for the single parameter constructor here to reuse the constructor that is taking threeparameters and this is what we call constructor delegation.The one parameter constructor on top is going to delegate the construction of its object to anotherconstructor and the syntax to do that is really simple.We can do constructor delegation using the syntax we see here.We have the constructor here.You see we have the function name, we have the parameters.Then we’re going to put a colon and specify the constructor.We want to call and specify the arguments we want to call it with.Okay, so this really looks like initializer list and it is an initializer list, but it is callinganother constructor and which constructor it is called.And if we look down, we’re going to see that we have a constructor that exactly takes three parametersand it is what we are passing here side param, the color and the shading information.And that’s what we pass in this constructor here.And the construction work for our object is no longer going to be done by this constructor here.It’s going to be done by the three parameter constructor.And when the job is done, we’re going to hit the body of the one parameter constructor here.So we need to think about the sequence of events when we have a constructor delegation involved.So what is going to happen here?The one parameter constructor is going to be called if you pass one parameter when you are creatingyour object.So before we get into the body of the one parameter constructor, the compiler is going to realize theconstructor delegation and it is going to call the three parameter constructor to do the actual objectcreation.Then the three parameter constructor is going to construct the object and it’s going to initialize theobject with the provided data.So we’re going to use the arguments that are passed in the initializer list of our one parameter constructorhere.And then after the object is created, we’re going to hit the body of the three parameter constructorbecause that’s the constructor that was called After the body of the three parameter constructor.We’re going to hit the body of the single parameter constructor and control is going to go back to Mainwhere we created the object.And one thing you should know is that all these calls to this constructor was can be seen in the callstack with the debugger.And you’re going to see this in a minute.But now what we really want to know is the syntax to do constructor delegation and understand what thisis going to do.It is going to delegate the work to build the object to another constructor.And the syntax to do this is to use an initializer list which is going to call the other constructor.And this is really cool.Okay, some of you might be asking, okay, constructor delegation is good and we have seen that wecould use initializer list to do that.But what if I call the three parameter constructor myself like I do here?Well, I hate to break this to you, but this is not going to do what you want.This is not going to do constructor delegation.This is just going to create a local object of square and it is going to be thrown away when the bodyof this function finishes executing.So if you really want to do constructor.Delegation initializer lists are your only bet.This is one cases where we can only do this using initializer lists and the importance of initializerlist is only getting better.And this is a case where we can only do this thing using initializer lists.Another thing you should note with constructor delegation is that if you have done a constructor delegationin your class, you can’t do any further initialization before or after the delegation call.So for example, if we look at the line here, we have our constructor delegation.We can’t put up another initializer which is going to initialize the position.If we do this, the compiler is going to throw a compiler error.It’s not allowed.The line here is going to try and put the initialization for the position in front of the delegationcall.But this is also going to be a compiler error and you can’t do that.This is disallowed by the compiler.Okay.So this is really how constructor delegation is going to work.And after you have done your constructor delegation, you can really do anything you want in the bodyof your one parameter constructor.Here, for example, we can take this chance to initialize the position member variable, which wasn’tinitialized by any of the initializer list.And we can do this and it is going to work.Now that you know this, we’re going to head over to Visual Studio code and show you this things inaction.Okay, Here we are in our working folder.The current project is constructor delegation.We are going to be reusing the code from the last lecture where we set up a square class.So let’s grab the code from there.We’re going to grab our classes and we’re going to grab the VS code folder because we’re going to beusing this for our settings and then we’re going to put this in our current project, which is constructeddelegation.Again, we’re going to grab this and open this in Visual Studio code and this is going to open our folder.The first thing we want to do is to open up the main CPP file and see that we have everything we needthere.We don’t really need to do the second call here because it’s going to just give us a compiler error.What we want to learn about here is constructor delegation and we’re just going to use our code likethis.We just want to use constructor delegation here.So let’s head over to our square class and really modify it.We’re going to add a net variable, which is going to be our shading and we’re going to add a doublevariable member variable, I should say, which is going to be our position.And we can open our square CPP file and close the left pane here because it is taking up space.So let’s go to the header file and we’re going to set up a constructor which is going to initializethree of these guys here.So let’s do that.We’re going to put in a constructor which is going to take three parameters.Let’s do that.We’re going to say Square and we’re going to say double side parameters.We’re going to say const STD string, we can do this.We’re going to use a reference and this is going to be color param and we’re going to say int shadingparam, this is going to be our thing and it’s not going to have anything by default here.And I think we can even take out the constructor we have in front here and make it a one param constructor.This is going to help us better in what we want to show in this lecture here.Can’t take out this semicolon.Let’s do this and this is it.Let’s go back to the CPP file and make sure that we have the implementations for these two constructors.We’re going to go to square dot CPP and we’re going to see that we no longer have a one parameter constructor.We can change this and change this to only initialize the first guy and the others are going to be leftout.But we can put in another constructor which is going to initialize three parameters or three membervariables I should say.So we’re going to say square and square and we’re going to have a double side.Param I should go to the header and copy these parameters and because I can’t remember how I named them,I can do that.So I’m going to put them in here and then we’re going to let’s check how we named our things.This should be m shading because it’s a member variable and this should be M position because it isa member variable.This is just trying to stay consistent with our naming convention here.We’re going to put in our initializer list.So we’re going to say M side, we’re going to initialize this with side param and we’re going to doM color and we’re going to initialize this with color param and we’re going to do M shading and we’regoing to initialize this with shading param.Hopefully you see that naming your things consistently is going to help out.Okay, now we have the constructor.In place.But we really have two constructors that are different and they are going to be constructing.Objects here.But sometimes we want to make one constructor reuse the work that we have done in another constructor.So suppose, for example, the three parameter constructor here is doing some heavy computations andwe don’t want to repeat all these computations in all these constructors and repeat our work.So a reasonable thing to do is to make the first constructor here, which is going to take one parameter.Use the constructor that is taking more parameters because we can pass default parameters when we callthe other constructor here, which takes more parameters.I realize this is a little bit convoluted, but stay with me.We’re going to explain these things step by step.Okay?So let’s go down here and put in a few stdcout statements.We’re going to say iostream here because we want to print things out and we’re going to say that theone parameter constructor was called.And down here we’re going to say that the three parameter constructor was called.So let’s go down here and put our body and make it aligned with what we did here.And our initializer list calls are not consistent here.We are using curlies, here we are using parentheses.I like to use curlies.So let’s do that so that we really stay consistent here.Let’s do this.We’re going to change this to use curly braces.And the last one is also going to use curly braces.This is going to be consistent here.So we have this we’re going to go inside and put in our Stdcout statement and we’re going to say thatthe three parameter constructor was called Let’s go back in Main and see what we have there.We don’t really want all this junk because we don’t need this anymore.We just want to see what is used to build our object here.So let’s take out the function here because we don’t really need it anymore either.And we can build we’re going to use GCC to build this.And you see, the build is good.If we bring up a terminal to run our program, we can clear and run Rooster.We’re going to see that one param constructor is called to build our object here, but we don’t wantthe one param to do the job alone.We want to reuse the three param constructor, to use the logic we have in here to build our objectand we can do constructor delegation.The syntax to do constructor delegation is really simple.You just use initializer lists.So we’re going to take out the initializer list we have here and we’re going to change it to call thethree parameter constructor.So we’re going to say Square and we’re going to pass it all the parameters that it expects.We only have one.So we can, for example, say side parameter here, let’s use Curly because that’s consistent.We’re going to say side param because that’s what we have here.So let’s do that.And for the others we’re going to just specify some values here to make the other constructor happy.So for example, we can specify a default value we want, let’s say black.You just need to be able to handle all this information in your three parameter constructor.So we just want to make it happy here.And for shading, let’s pass in a sex.Let’s suppose this makes sense for whatever application we are building here and you see that VisualStudio code is going to accept this syntax.We have no squiggly lines, so this must be working here.But I want you to understand the sequence of events when we set up a call like this.But before we analyze that, let’s build and see what is going to happen here.So let’s build with GCC.We’re going to see that the world is good.So the calls here are going to be working.But let’s see what we see in the terminal here.If we run our executable, we’re going to see that the three parameter constructor was called.Then the one parameter constructor was called.So this hopefully proves that even if we call the single parameter constructor, the compiler is goingto use the three parameter constructor to build our object.After the object is created, we’re going to hit the body of the three parameter constructor becausethat’s what constructed our object.Then control is to go back to our constructor and give us a chance to do whatever we want to do in ourone parameter constructor here.We can even prove this with a debugger.So let’s put a breakpoint on line seven here and put a breakpoint on line 13 here in the body of eachconstructor.And then we’re going to try and run this through the debugger.We are going to go to our run tab here, which is what we always use to run the debugger.And by the way, you can go to the run menu here and hit start debugging.This is going to start debugging your application just like we do here.But I like to do things explicitly by coming here.So I.I am going to start my debugging session and we don’t really need to see any other thing.The first thing we see is that we have hit the body of the three parameter constructor and look at whatwe have in Main.In Main we are calling the one parameter constructor, but we have hit the body of our three parameterconstructor.So what is going to happen is when we call the one parameter constructor, the compiler is going tohit the constructor here, but it’s going to see that it is delegating the construction work to thethree parameter constructor.Then the three parameter constructor is going to be called.We’re going to hit its body.If we execute and get out of it, you’re going to see that we’re going to hit the body of the one parameterconstructor I just hit next a couple of times, and then I’m going to come in here.And if we hit step over, we’re going to print the message here.And if we go to our terminal, we’re going to see that the messages were printed out.And if we come out of this, we’re going to come back to Main and our object is going to be alreadyconstructed.And if we look inside, we’re going to see our information that was used to create the object here.This is really cool.And we can see all this data in our local variables here.So, for example, you’re going to see that the color let’s see if we can see this.You’re going to see that the color is black.It is really hidden in all these options here.But we can hunt that down and find that.So we have the color, we have the shading, we have the position, which is really a junk value becausewe didn’t really initialize this, but you see that we can see our data.And the main message in this lecture is that we can do constructor delegation and the syntax to do thatis in our square CPP file.We can bring this back and you have to use the initializer list as you see here.So this is the syntax and you need to know this.Okay, so now that you know this, we’re going to address the problem that some of you might be askingabout what we are doing here.So what if we don’t use our initializer list?Let’s comment this out anyway.So we’re going to take out all this.We can comment this out and we don’t use an initializer list and we call the constructor directly inour body, the body of our one parameter constructor.What if we do that?Let’s do that.So we’re going to say square and we’re going to pass in side param because we have access to that andthen we’re going to pass the other parameters here.The color is going to be black and the shading is going to be six.Let’s do this and you’re going to see that the compiler is really not going to complain, but it isnot going to do what you think it is going to do.Let’s try and build the program and really see what is happening here.Hopefully we can see that we are going to build with JCC.You’re going to see that the world is good, but we’re going to run Rooster and we’re going to see thatthree parameter constructor called one parameter constructor called This still looks like what we hadin the last few runs, but what this is really doing, it’s not doing the constructor delegation.This is constructing your object and destroying it on the spot because this is going to be a local objectfor our square class.We can prove this by putting some message in our destructor.So let’s say Stdcout Square object destroyed and we’re going to see that the three parameter constructoris going to be called, but its destructor is going to be called before we hit the end of this functionbody here.Let’s build and see if this actually is easier to see.You’re going to see that the world is good and we’re going to clear and run Rooster.We’re going to see that once we hit the body of the square constructor.The three parameter constructor is going to be called, which is what we have here.And the output statement is going to be coming from this guy here.But because the compiler sees that this guy will never be used, it is a temporary object.The compiler is going to destroy it right away and we’re going to see square object destroyed here.Then we’re going to print this one parameter constructor called Message here.And then we’re going to say that the square object that we built in the main function here was destroyed.You really need to understand these things.Hopefully you see that if you call your constructor directly in the body of your constructor here,this is not going to do constructor delegation.And I want you to know this.So let’s take this out and bring in our initializer list back.And this is going to do proper constructor delegation and let’s try and build and run this program tosee if we see different output.We’re going to clear and run Rooster.We’re going to see that only the three parameter constructor is going to be called The one parameterconstructor is going to be called.After we do the proper object initialization, we’re going to hit this body here.We’re going to hit.One param constructor called and we see that we destroy only one square object because we don’t havea local object constructed in the body of one param constructor here.Hopefully this is clear now.Another thing I want you to see is that we can’t do further initialization if we are doing constructordelegation here.So for example on this one param constructor of ours, we can’t try and initialize the position membervariable here.If we do that, we’re going to get a compiler error.So let’s try and do that before.So we’re going to say M position and we’re going to put in any position.Let’s put in 8.0 and put a comma here and we’re going to put a comma after this.And let’s use curly braces here.I can’t really remember using curly braces here, so let’s do that.If we do this, we’re going to get a compiler error.You see that we have a squiggly line.The compiler is not going to support this.If you are using constructor delegation, the constructor delegation should be the only thing you havein your initializer list.If we build this, we’re going to get some weird compiler error.And let’s see that constructor delegation follows Member initializer list for square position and thisis not allowed.We’re going to have a compiler error if we try and put this position after the constructor delegation,we’re also going to get the same thing because the compiler doesn’t allow this.This is just how the compilers are wired and we have to live with this.If we do this, we’re also going to get a squiggly line because this is not allowed.Let’s world with GCC to show you this.We’re going to see a build error and it is basically going to be the same thing and you shouldn’t reallydo this.So we can’t have any further initialization before or after our constructor delegation here.Let’s build and see that.Now this is building fine because we should prove this.We see the build is good, so the compiler is happy.Okay, So I think this really wraps up what I had to share with you in this lecture.But before I let you go, I want you to see that you can really do anything once you hit the body ofyour one parameter constructor.Here, for example, we can take this chance to initialize our position member variable, which is notinitialized by any of the initializer lists we have in here.So we can go in here and for example, put a value in our M position member variable.We can do this.So we’re going to say M position equals 45.8, for example, and this is going to be accepted by thecompiler.If we build, we’re going to see that the build is going to be good.Okay, the build is good and this is really all I hope now you know that you can do constructor delegationto offload the job to build your object to other constructors.Those constructors are going to take over your job to do construction and when they are done doing thejob, they are going to give you a chance to do whatever you want in your delegating constructor.As we see in this one Param constructor here, this is really all I had to share in this lecture.I hope you found it interesting.We are going to stop here in this one.In the next one we’re going to learn about copy constructors.Go ahead and finish up here and meet me there.


🤖 Assistant

好的,没问题!非常乐意帮助你学习 C++ 中的构造函数委托 (constructor delegation)。这确实是一个非常有用的特性,能让你的代码更简洁、更易于维护。

让我们开始吧!


1. 标题和简介

标题: C++ 构造函数委托 (Constructor Delegation) 精讲:让构造函数互相协作

简介: 本节课我们将学习 C++ 中的一个重要特性——构造函数委托。它允许一个构造函数 (constructor) 调用同一个类中的另一个构造函数来完成对象的初始化,从而避免代码重复,提高代码的可维护性。


2. 详细解释

想象一下,你在设计一个类,比如一个 Square (正方形) 类。这个类可能有很多成员变量 (member variables),例如边长、颜色、阴影样式、屏幕位置等等。

cpp
1
2
3
4
5
6
7
8
9
10
11
// 假设的 Square 类
class Square {
private:
double m_side; // 边长
std::string m_color; // 颜色
int m_shading; // 阴影样式 (整数表示)
double m_position_x; // X轴位置
// 可能还有 m_position_y 等等
public:
// ... 构造函数和其他方法
};

现在,你可能需要提供多种方式来创建 Square 对象:

  • 只提供边长,其他使用默认值。
  • 提供边长、颜色和阴影样式,位置使用默认值。
  • 提供所有参数。

如果为每种情况都写一个完整的构造函数,并且在每个构造函数中都分别初始化这些成员变量,你会发现很多初始化逻辑是重复的。

cpp
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
// 不使用构造函数委托的例子 (冗余代码)
#include <string>
#include <iostream>

class Square {
private:
double m_side;
std::string m_color;
int m_shading;
double m_position_x;

public:
// 构造函数1: 只接受边长
Square(double side_param) {
m_side = side_param;
m_color = "black"; // 默认颜色
m_shading = 0; // 默认阴影
m_position_x = 0.0; // 默认位置
std::cout << "单参数构造函数被调用 (独立实现)\n";
}

// 构造函数2: 接受边长、颜色、阴影
Square(double side_param, const std::string& color_param, int shading_param) {
m_side = side_param;
m_color = color_param;
m_shading = shading_param;
m_position_x = 0.0; // 默认位置 (仍然有重复的默认值设置)
std::cout << "三参数构造函数被调用 (独立实现)\n";
}
// ... 可能还有其他构造函数

void print_info() const {
std::cout << "Side: " << m_side
<< ", Color: " << m_color
<< ", Shading: " << m_shading
<< ", Position X: " << m_position_x << std::endl;
}
};

上面这种方式,m_position_x = 0.0; 这行代码,以及其他可能的默认值设置,就可能在多个构造函数中重复出现。如果以后 m_position_x 的默认值需要修改,你就得修改所有相关的构造函数,很容易遗漏。

构造函数委托 (Constructor Delegation) 就是为了解决这个问题而生的。

它允许一个构造函数将对象的实际构建工作“委托”给同一个类中的另一个构造函数。

语法:

构造函数委托的语法非常简洁,它发生在构造函数的 初始化列表 (initializer list) 位置。

cpp
1
2
3
4
ClassName(parameters_for_this_constructor) : AnotherConstructorInSameClass(arguments_for_delegated_constructor) {
// 这个构造函数自己的函数体 (body)
// 在被委托的构造函数执行完毕后,这里的代码才会执行
}
  • ClassName(parameters_for_this_constructor): 这是定义当前构造函数(我们称之为“委托构造函数 (delegating constructor)”)。
  • :: 冒号,标志着初始化列表的开始。
  • AnotherConstructorInSameClass(arguments_for_delegated_constructor): 这就是委托的核心。它看起来像是在调用同一个类的另一个构造函数(我们称之为“目标构造函数 (target constructor)”或“被委托的构造函数”)。你传递给目标构造函数的参数可以是委托构造函数的参数,也可以是常量值或默认值。
  • { ... }: 委托构造函数的函数体。

执行顺序:

当你使用构造函数委托时,执行顺序非常重要:

  1. 当你调用一个委托构造函数来创建对象时,编译器首先会识别出这是一个委托调用。
  2. 目标构造函数 (被委托的那个) 会首先被执行。
    • 目标构造函数自己的初始化列表(如果有的话,但它不能再委托给别的构造函数了,除非是不同的委托链)会先执行。
    • 然后目标构造函数的函数体会执行。
  3. 在目标构造函数完全执行完毕后,控制权返回到委托构造函数
  4. 委托构造函数的函数体 (body) 才会执行。

一个关键点: 如果一个构造函数使用了委托,那么在它的初始化列表中,不能再对其他成员变量进行初始化了。委托调用必须是初始化列表中的唯一项(相对于成员初始化而言)。

例如,这是 错误 的:

cpp
1
2
3
4
5
6
7
8
9
10
11
12
// 错误示范!
class MyClass {
int a;
int b;
public:
MyClass(int val_a) : MyClass(val_a, 100), b(val_a) { // 错误!不能同时委托和初始化其他成员
// ...
}
MyClass(int val_a, int val_b) : a(val_a), b(val_b) {
// ...
}
};

正确的做法是,将所有初始化工作都交给目标构造函数,或者在委托构造函数的 函数体 内进行某些操作(但此时成员变量已经被目标构造函数初始化过了)。

为什么不能在构造函数体内部直接调用另一个构造函数来实现委托?

你可能会想:“我能不能在一个构造函数的函数体里,像调用普通函数一样调用另一个构造函数呢?” 比如:

cpp
1
2
3
4
5
6
7
8
9
// 这是一个常见的误解,并不能实现构造函数委托!
Square(double side_param) {
std::cout << "单参数构造函数的函数体开始\n";
Square(side_param, "default_color", 0); // 警告!这不是委托!
// m_side = side_param; // 假设这个构造函数原本是想做这些事的
// m_color = "default_color";
// m_shading = 0;
std::cout << "单参数构造函数的函数体结束\n";
}

如果你这样做,Square(side_param, "default_color", 0); 并不会 在当前正在构造的对象上调用另一个构造函数。相反,它会创建一个全新的、临时的、局部的 Square 对象。这个临时对象在这一行代码执行完毕后,如果没有任何引用指向它,它通常会立即被销毁。你原本想要初始化的那个对象,其成员变量可能仍然是未初始化或者处于不确定状态(除非你在后续代码中手动赋值)。

所以,实现构造函数委托的唯一正确方法就是使用初始化列表的特殊语法。

在委托构造函数的函数体中可以做什么?

虽然在使用了委托的构造函数的初始化列表中不能再初始化其他成员,但你仍然可以在它的 函数体 (body) 中执行其他逻辑。这个时候,对象已经被目标构造函数完全构建好了。你可以在这里进行一些额外的设置、打印日志、或者执行一些不适合放在目标构造函数中的特定于此委托构造函数的逻辑。

例如,在我们的 Square 例子中,假设目标构造函数没有初始化 m_position_x,我们可以在委托构造函数的函数体中完成:

cpp
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
// Square.h
// class Square {
// private:
// double m_side;
// std::string m_color;
// int m_shading;
// double m_position_x;
// public:
// Square(double side_param); // 委托构造函数
// Square(double side_param, const std::string& color_param, int shading_param); // 目标构造函数
// // ...
// };

// Square.cpp
Square::Square(double side_param, const std::string& color_param, int shading_param)
: m_side{side_param}, m_color{color_param}, m_shading{shading_param}, m_position_x{0.0} { // 目标构造函数初始化所有(或大部分)
std::cout << "目标 (三参数) 构造函数被调用。\n";
// m_position_x = 0.0; // 或者在这里初始化,如果逻辑更复杂
}

Square::Square(double side_param)
: Square(side_param, "default_black", 1) { // 委托给三参数构造函数
std::cout << "委托 (单参数) 构造函数被调用。\n";
// 此时,m_side, m_color, m_shading 已经被目标构造函数初始化
// m_position_x 也被目标构造函数初始化为 0.0
// 如果需要,可以在这里覆盖或做额外处理
// 比如: this->m_position_x = specific_value_for_this_constructor;
// 但通常,如果目标构造函数已经处理了所有必要的初始化,这里可能为空。
}

总结一下构造函数委托的好处:

  1. 减少代码重复 (Code Duplication): 这是最主要的好处。核心的初始化逻辑只需要写在一个(或少数几个)目标构造函数中。
  2. 提高可维护性: 如果需要修改初始化逻辑(比如某个成员的默认值),只需要修改目标构造函数,所有委托给它的构造函数都会自动受益。
  3. 更清晰的意图: 它清楚地表明某些构造函数是基于其他构造函数来构建对象的。

3. 代码示例

让我们用一个更完整的 Square 类来演示构造函数委托。

Square.h (头文件)

cpp
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
#ifndef SQUARE_H
#define SQUARE_H

#include <string>
#include <iostream> // 为了在构造函数中打印信息

class Square {
private:
double m_side;
std::string m_color;
int m_shading;
double m_position_x; // 假设还有一个位置成员

public:
// 目标构造函数: 初始化所有核心成员
Square(double side_param, const std::string& color_param, int shading_param, double position_x_param);

// 委托构造函数1: 只提供边长,颜色、阴影和位置使用默认值
// 它将委托给上面的四参数构造函数
Square(double side_param);

// 委托构造函数2: 提供边长和颜色,阴影和位置使用默认值
// 它也将委托给上面的四参数构造函数
Square(double side_param, const std::string& color_param);

// 析构函数 (destructor),用于演示临时对象问题
~Square();

void print_info() const;
};

#endif // SQUARE_H

Square.cpp (实现文件)

cpp
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
58
59
60
61
62
63
64
65
66
67
#include "Square.h"

// 目标构造函数 (Target Constructor)
Square::Square(double side_param, const std::string& color_param, int shading_param, double position_x_param)
: m_side{side_param}, m_color{color_param}, m_shading{shading_param}, m_position_x{position_x_param} {
std::cout << "目标构造函数 (4个参数) 被调用. Side: " << m_side << ", Color: " << m_color << ", Shading: " << m_shading << ", PosX: " << m_position_x << std::endl;
}

// 委托构造函数1 (Delegating Constructor)
Square::Square(double side_param)
: Square(side_param, "Default Black", 0, 0.0) { // 委托给四参数构造函数
// 上面那行是委托调用,它必须是初始化列表中的唯一(关于成员初始化)的项
// 不能在这里写 : m_some_other_member(value)
std::cout << "委托构造函数 (1个参数) 的函数体被调用. Side: " << m_side << std::endl;
// 此时对象已经由目标构造函数构建完毕
// 你可以在这里做一些这个构造函数特有的额外事情
// 例如,如果 m_position_x 需要根据 side_param 做特殊计算,而目标构造函数只设置了通用默认值
// this->m_position_x = side_param * 0.5; // 举例,覆盖或调整
}

// 委托构造函数2 (Delegating Constructor)
Square::Square(double side_param, const std::string& color_param)
: Square(side_param, color_param, 5, 0.0) { // 委托给四参数构造函数
std::cout << "委托构造函数 (2个参数) 的函数体被调用. Side: " << m_side << ", Color: " << m_color << std::endl;
}

Square::~Square() {
std::cout << "析构函数被调用 for Square with side: " << m_side << std::endl;
}

void Square::print_info() const {
std::cout << "Square Info -> Side: " << m_side
<< ", Color: " << m_color
<< ", Shading: " << m_shading
<< ", Position X: " << m_position_x << std::endl;
}

// 演示在构造函数体内“调用”构造函数的错误做法
void demonstrate_wrong_delegation_attempt(double side) {
std::cout << "\n--- 演示错误委托尝试 ---" << std::endl;
// 这个构造函数本身是正确的,我们只是用它来演示一个错误的调用方法
class TempSquare {
public:
double val;
TempSquare(double v) : val(v) {
std::cout << " TempSquare(" << v << ") 构造函数被调用 (为了演示错误做法)." << std::endl;
}
TempSquare(double v, const std::string& c) : val(v) {
std::cout << " TempSquare(" << v << ", " << c << ") 构造函数被调用." << std::endl;
}
~TempSquare() {
std::cout << " TempSquare(" << val << ") 析构函数被调用." << std::endl;
}

// 错误尝试:在函数体内调用构造函数
TempSquare(double v, int /*dummy to differentiate*/) : val(0.0) { // 初始化 val 以避免未定义行为
std::cout << " TempSquare(" << v << ", int) 构造函数体开始. 当前 val: " << this->val << std::endl;
TempSquare(v, "red"); // 这会创建一个临时对象,然后立即销毁它!
// 它不会修改当前正在构造的 TempSquare 对象的 val
std::cout << " TempSquare(" << v << ", int) 构造函数体结束. 当前 val: " << this->val << " (注意,没有被修改成 " << v << ")\n";
}
};

TempSquare ts1(side, 123); // 调用那个演示错误委托的构造函数
std::cout << " ts1.val after construction: " << ts1.val << std::endl;
std::cout << "--- 演示错误委托尝试结束 ---" << std::endl;
}

main.cpp (主程序)

cpp
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
#include "Square.h"
#include <iostream>

int main() {
std::cout << "创建 s1 (使用1个参数的构造函数):\n";
Square s1(10.0);
s1.print_info();
std::cout << std::endl;

std::cout << "创建 s2 (使用2个参数的构造函数):\n";
Square s2(5.0, "Blue");
s2.print_info();
std::cout << std::endl;

std::cout << "创建 s3 (使用4个参数的构造函数 - 目标构造函数直接调用):\n";
Square s3(2.0, "Green", 3, 25.5);
s3.print_info();
std::cout << std::endl;

// 演示错误委托尝试
demonstrate_wrong_delegation_attempt(7.7);
std::cout << std::endl;

std::cout << "程序结束前,s1, s2, s3 将按逆序销毁。\n";
return 0;
}

编译和运行:
你需要将 Square.cppmain.cpp 一起编译。例如使用 g++:
g++ main.cpp Square.cpp -o constructor_delegation_demo -std=c++11
(构造函数委托是 C++11 引入的特性,所以确保你的编译器支持 C++11 或更高版本,并可能需要指定 -std=c++11 或更高,如 -std=c++17)

然后运行: ./constructor_delegation_demo

预期输出分析:
你会看到:

  1. 创建 s1 时:
    • 首先输出 “目标构造函数 (4个参数) 被调用…” (因为 Square(double) 委托给了它)。
    • 然后输出 “委托构造函数 (1个参数) 的函数体被调用…”。
  2. 创建 s2 时:
    • 首先输出 “目标构造函数 (4个参数) 被调用…” (因为 Square(double, string) 委托给了它)。
    • 然后输出 “委托构造函数 (2个参数) 的函数体被调用…”。
  3. 创建 s3 时:
    • 只输出 “目标构造函数 (4个参数) 被调用…” (因为它就是目标构造函数,没有委托)。
  4. demonstrate_wrong_delegation_attempt 中:
    • 你会看到 TempSquare(v, "red") 确实调用了对应的构造函数,但紧接着它的析构函数也被调用了,表明它是一个生命周期短暂的临时对象。原对象的 val 并没有被这个临时对象的构造所影响。
  5. 最后,当 main 函数结束时,s3, s2, s1 (按栈的逆序) 的析构函数会被调用。

这个例子清晰地展示了构造函数委托的流程和好处,以及错误尝试的后果。


4. QA 闪卡 (QA Flash Cards)

闪卡 1
Q: 什么是 C++ 中的构造函数委托 (constructor delegation)?
A: 一个构造函数调用同一个类中的另一个构造函数来完成对象的初始化。

闪卡 2
Q: 使用构造函数委托的主要目的是什么?
A: 避免构造函数之间的代码重复,提高代码的可维护性。

闪卡 3
Q: 如何在 C++ 中实现构造函数委托 (语法)?
A: 在委托构造函数的初始化列表 (initializer list) 中,使用 : ClassName(arguments) 的形式调用目标构造函数。

闪卡 4
Q: 当使用构造函数委托时,哪个构造函数的函数体先执行?目标构造函数还是委托构造函数?
A: 目标构造函数的函数体先执行,然后是委托构造函数的函数体。

闪卡 5
Q: 如果一个构造函数委托给另一个构造函数,它还能在自己的初始化列表中初始化其他成员变量吗?
A: 不可以。委托调用必须是初始化列表中关于成员初始化的唯一项。

闪卡 6
Q: 在一个构造函数的函数体 (body) 中直接调用 ClassName(...) 会实现委托吗?
A: 不会。这会创建一个临时的、局部的 ClassName 对象,而不是委托给当前对象的另一个构造函数。


5. 常见误解或易犯错误

  1. 在函数体内调用构造函数进行委托:

    • 误解: MyClass() { MyClass(defaultValue); } 可以实现委托。
    • 事实: 这会创建一个新的、临时的 MyClass 对象,它与当前正在构造的对象无关,并且通常会立即被销毁。当前对象的成员可能仍未初始化或初始化不正确。
    • 正确做法: 必须使用初始化列表语法 MyClass() : MyClass(defaultValue) {}
  2. 在委托的同时尝试初始化其他成员变量:

    • 误解: MyClass(int x) : MyClass(x, 0), m_someOtherMember(10) {} 是允许的。
    • 事实: 这是编译错误。如果一个构造函数使用了委托,那么该委托调用必须是其初始化列表中唯一负责(直接或间接)成员初始化的项。你不能既委托构造,又在同一个初始化列表中初始化其他成员。
    • 正确做法: 要么让目标构造函数负责所有初始化,要么在委托构造函数的 函数体 中进行后续的赋值操作(此时对象已被目标构造函数初始化)。
  3. 对执行顺序的困惑:

    • 误解: 委托构造函数的函数体先执行,然后再调用目标构造函数。
    • 事实: 顺序是:委托构造函数被调用 -> 目标构造函数的初始化列表和函数体被执行 -> 委托构造函数的函数体被执行。
  4. 忘记提供所有必要的参数给目标构造函数:

    • 如果目标构造函数需要多个参数,你在委托时必须提供所有这些参数,无论是通过传递委托构造函数的参数,还是使用默认值/常量。
  5. 过度委托或形成委托环路 (虽然编译器通常会捕捉到直接环路):

    • 例如:C1() : C2() {}C2() : C1() {}。这会导致无限递归,编译器会报错。设计委托链时要确保有一个明确的“最终”目标构造函数。
  6. 不理解委托构造函数的函数体的执行时机:

    • 委托构造函数的函数体是在对象已经由目标构造函数完全构建之后才执行的。这意味着在委托构造函数的函数体内,所有成员变量(至少那些由目标构造函数初始化的)都已经有了有效的值。你可以利用这一点来进行一些只有这个特定委托构造函数才需要的额外调整或操作。

6. 编码练习

现在,轮到你来试试身手了!下面是一个 Rectangle (矩形) 类的骨架。你需要完成它,使得其中一个构造函数委托给另一个构造函数。

Rectangle.h

cpp
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
#ifndef RECTANGLE_H
#define RECTANGLE_H

#include <string>
#include <iostream>

class Rectangle {
private:
double m_width;
double m_height;
std::string m_color;
int m_border_style; // 0 for none, 1 for solid, 2 for dashed

public:
// 目标构造函数: 初始化所有成员
Rectangle(double width, double height, const std::string& color, int border_style)
: m_width{width}, m_height{height}, m_color{color}, m_border_style{border_style} {
std::cout << "目标 Rectangle 构造函数 (4个参数) 被调用.\n";
std::cout << " Width: " << m_width << ", Height: " << m_height
<< ", Color: " << m_color << ", Border: " << m_border_style << std::endl;
}

// 练习1: 这是一个只接受宽度和高度的构造函数。
// 请让它委托给上面的四参数构造函数。
// 为 color 提供默认值 "Red"。
// 为 border_style 提供默认值 1 (solid)。
Rectangle(double width, double height)
// TODO: 在这里添加构造函数委托调用,替换掉下面的空初始化列表
: /* TODO: 你的代码在这里 */ {
std::cout << "委托 Rectangle 构造函数 (2个参数) 的函数体被调用.\n";
// 你可以在这里添加一些额外的逻辑,如果需要的话
}

// 练习2: 这是一个只接受宽度、高度和颜色的构造函数。
// 请让它也委托给四参数构造函数。
// 为 border_style 提供默认值 0 (none)。
Rectangle(double width, double height, const std::string& color)
// TODO: 在这里添加构造函数委托调用,替换掉下面的空初始化列表
: /* TODO: 你的代码在这里 */ {
std::cout << "委托 Rectangle 构造函数 (3个参数) 的函数体被调用.\n";
}


void print_info() const {
std::cout << "Rectangle Info -> Width: " << m_width
<< ", Height: " << m_height
<< ", Color: " << m_color
<< ", Border Style: " << m_border_style << std::endl;
}

~Rectangle() {
std::cout << "Rectangle (" << m_width << "x" << m_height << ") 析构.\n";
}
};

#endif // RECTANGLE_H

main.cpp (用于测试你的代码)

cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "Rectangle.h"
#include <iostream>

int main() {
std::cout << "--- 创建 rect1 (使用2个参数) ---\n";
Rectangle rect1(10.0, 5.0);
rect1.print_info();
std::cout << std::endl;

std::cout << "--- 创建 rect2 (使用3个参数) ---\n";
Rectangle rect2(7.0, 3.0, "Blue");
rect2.print_info();
std::cout << std::endl;

std::cout << "--- 创建 rect3 (使用4个参数) ---\n";
Rectangle rect3(2.0, 2.0, "Green", 2);
rect3.print_info();
std::cout << std::endl;

std::cout << "程序即将结束。\n";
return 0;
}

请你修改 Rectangle.h 中标记为 /* TODO: 你的代码在这里 */ 的部分,实现构造函数委托。完成后,将你修改后的 Rectangle.h 文件内容告诉我,我会帮你检查并给出反馈。

祝你编码愉快!


🧑‍💻 User

cpp
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
#ifndef RECTANGLE_H
#define RECTANGLE_H

#include <string>
#include <iostream>

class Rectangle {
private:
double m_width;
double m_height;
std::string m_color;
int m_border_style; // 0 for none, 1 for solid, 2 for dashed

public:
// 目标构造函数: 初始化所有成员
Rectangle(double width, double height, const std::string& color, int border_style)
: m_width{width}, m_height{height}, m_color{color}, m_border_style{border_style} {
std::cout << "目标 Rectangle 构造函数 (4个参数) 被调用.\n";
std::cout << " Width: " << m_width << ", Height: " << m_height
<< ", Color: " << m_color << ", Border: " << m_border_style << std::endl;
}

// 练习1: 这是一个只接受宽度和高度的构造函数。
// 请让它委托给上面的四参数构造函数。
// 为 color 提供默认值 "Red"。
// 为 border_style 提供默认值 1 (solid)。
Rectangle(double width, double height)
// TODO: 在这里添加构造函数委托调用,替换掉下面的空初始化列表
: Rectangle(width, height, "red", 1)/* TODO: 你的代码在这里 */ {
std::cout << "委托 Rectangle 构造函数 (2个参数) 的函数体被调用.\n";
// 你可以在这里添加一些额外的逻辑,如果需要的话
}

// 练习2: 这是一个只接受宽度、高度和颜色的构造函数。
// 请让它也委托给四参数构造函数。
// 为 border_style 提供默认值 0 (none)。
Rectangle(double width, double height, const std::string& color)
// TODO: 在这里添加构造函数委托调用,替换掉下面的空初始化列表
: Rectangle(width, height, color, 1)/* TODO: 你的代码在这里 */ {
std::cout << "委托 Rectangle 构造函数 (3个参数) 的函数体被调用.\n";
}


void print_info() const {
std::cout << "Rectangle Info -> Width: " << m_width
<< ", Height: " << m_height
<< ", Color: " << m_color
<< ", Border Style: " << m_border_style << std::endl;
}

~Rectangle() {
std::cout << "Rectangle (" << m_width << "x" << m_height << ") 析构.\n";
}
};

#endif // RECTANGLE_H

🤖 Assistant

太棒了!你已经尝试完成了这个练习,这非常好!我们一起来看看你的代码。


7. 评价你的解答、正确答案及解释

你的解答:

cpp
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
#ifndef RECTANGLE_H
#define RECTANGLE_H

#include <string>
#include <iostream>

class Rectangle {
private:
double m_width;
double m_height;
std::string m_color;
int m_border_style; // 0 for none, 1 for solid, 2 for dashed

public:
// 目标构造函数: 初始化所有成员
Rectangle(double width, double height, const std::string& color, int border_style)
: m_width{width}, m_height{height}, m_color{color}, m_border_style{border_style} {
std::cout << "目标 Rectangle 构造函数 (4个参数) 被调用.\n";
std::cout << " Width: " << m_width << ", Height: " << m_height
<< ", Color: " << m_color << ", Border: " << m_border_style << std::endl;
}

// 练习1: 这是一个只接受宽度和高度的构造函数。
// 请让它委托给上面的四参数构造函数。
// 为 color 提供默认值 "Red"。
// 为 border_style 提供默认值 1 (solid)。
Rectangle(double width, double height)
// TODO: 在这里添加构造函数委托调用,替换掉下面的空初始化列表
: Rectangle(width, height, "red", 1)/* 你的代码在这里 */ { // <-- 你的实现1
std::cout << "委托 Rectangle 构造函数 (2个参数) 的函数体被调用.\n";
// 你可以在这里添加一些额外的逻辑,如果需要的话
}

// 练习2: 这是一个只接受宽度、高度和颜色的构造函数。
// 请让它也委托给四参数构造函数。
// 为 border_style 提供默认值 0 (none)。
Rectangle(double width, double height, const std::string& color)
// TODO: 在这里添加构造函数委托调用,替换掉下面的空初始化列表
: Rectangle(width, height, color, 1)/* 你的代码在这里 */ { // <-- 你的实现2
std::cout << "委托 Rectangle 构造函数 (3个参数) 的函数体被调用.\n";
}


void print_info() const {
std::cout << "Rectangle Info -> Width: " << m_width
<< ", Height: " << m_height
<< ", Color: " << m_color
<< ", Border Style: " << m_border_style << std::endl;
}

~Rectangle() {
std::cout << "Rectangle (" << m_width << "x" << m_height << ") 析构.\n";
}
};

#endif // RECTANGLE_H

评价:

  • 练习1 (Rectangle(double width, double height)):

    • : Rectangle(width, height, "red", 1)
    • 做得非常好! 这里的委托调用是完全正确的。你正确地将 widthheight 参数传递了下去,并为 color 提供了字符串字面量 "red" (它会隐式转换为 std::string),为 border_style 提供了整数 1。这完全符合题目要求。
  • 练习2 (Rectangle(double width, double height, const std::string& color)):

    • : Rectangle(width, height, color, 1)
    • 这里有一个小小的偏差。 题目要求是为 border_style 提供默认值 0 (none),但你的代码中提供的是 1。不过,委托的语法本身是正确的,只是提供的默认值与要求不符。

总体评分:4.5 / 5 星 ⭐⭐⭐⭐✨

你对构造函数委托的语法和基本用法掌握得很好!只是在练习2中对题目要求的细节稍有疏忽。这是一个很容易修正的小问题。


正确答案及解释:

下面是修正了练习2中默认值的 Rectangle.h

cpp
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
#ifndef RECTANGLE_H
#define RECTANGLE_H

#include <string>
#include <iostream>

class Rectangle {
private:
double m_width;
double m_height;
std::string m_color;
int m_border_style; // 0 for none, 1 for solid, 2 for dashed

public:
// 目标构造函数 (Target Constructor): 初始化所有成员
Rectangle(double width, double height, const std::string& color, int border_style)
: m_width{width}, m_height{height}, m_color{color}, m_border_style{border_style} {
std::cout << "目标 Rectangle 构造函数 (4个参数) 被调用.\n";
std::cout << " Width: " << m_width << ", Height: " << m_height
<< ", Color: " << m_color << ", Border: " << m_border_style << std::endl;
}

// 练习1: 委托构造函数 (Delegating Constructor)
// 为 color 提供默认值 "Red"。
// 为 border_style 提供默认值 1 (solid)。
Rectangle(double width, double height)
: Rectangle(width, height, "Red", 1) { // 正确: 委托给四参数构造函数
std::cout << "委托 Rectangle 构造函数 (2个参数) 的函数体被调用.\n";
}

// 练习2: 委托构造函数 (Delegating Constructor)
// 为 border_style 提供默认值 0 (none)。
Rectangle(double width, double height, const std::string& color)
: Rectangle(width, height, color, 0) { // 正确: 委托给四参数构造函数,border_style 修正为 0
std::cout << "委托 Rectangle 构造函数 (3个参数) 的函数体被调用.\n";
}


void print_info() const {
std::cout << "Rectangle Info -> Width: " << m_width
<< ", Height: " << m_height
<< ", Color: " << m_color
<< ", Border Style: " << m_border_style << std::endl;
}

~Rectangle() {
std::cout << "Rectangle (" << m_width << "x" << m_height << ") 析构.\n";
}
};

#endif // RECTANGLE_H

解释:

  1. Rectangle(double width, double height) 的委托:

    • : Rectangle(width, height, "Red", 1)
    • 我们调用了 Rectangle(double, double, const std::string&, int) 这个目标构造函数。
    • width (来自两参数构造函数的参数) 被传递给目标构造函数的第一个参数。
    • height (来自两参数构造函数的参数) 被传递给目标构造函数的第二个参数。
    • "Red" (一个C风格字符串字面量,会自动转换为 std::string 类型) 作为颜色传递给目标构造函数的第三个参数。
    • 1 作为边框样式传递给目标构造函数的第四个参数。
    • 之后,委托 Rectangle 构造函数 (2个参数) 的函数体被调用. 这行会打印出来。
  2. Rectangle(double width, double height, const std::string& color) 的委托:

    • : Rectangle(width, height, color, 0)
    • 同样,我们调用了四参数的目标构造函数。
    • widthheight 直接传递。
    • color (来自三参数构造函数的参数) 被传递给目标构造函数的第三个参数。
    • 0 (根据题目修正后的值) 作为边框样式传递给目标构造函数的第四个参数。
    • 之后,委托 Rectangle 构造函数 (3个参数) 的函数体被调用. 这行会打印出来。

使用 main.cpp 测试修正后的代码,你会得到如下输出,清晰地展示了委托的流程:

Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
--- 创建 rect1 (使用2个参数) ---
目标 Rectangle 构造函数 (4个参数) 被调用.
Width: 10, Height: 5, Color: Red, Border: 1
委托 Rectangle 构造函数 (2个参数) 的函数体被调用.
Rectangle Info -> Width: 10, Height: 5, Color: Red, Border Style: 1

--- 创建 rect2 (使用3个参数) ---
目标 Rectangle 构造函数 (4个参数) 被调用.
Width: 7, Height: 3, Color: Blue, Border: 0
委托 Rectangle 构造函数 (3个参数) 的函数体被调用.
Rectangle Info -> Width: 7, Height: 3, Color: Blue, Border Style: 0

--- 创建 rect3 (使用4个参数) ---
目标 Rectangle 构造函数 (4个参数) 被调用.
Width: 2, Height: 2, Color: Green, Border: 2
Rectangle Info -> Width: 2, Height: 2, Color: Green, Border Style: 2

程序即将结束。
Rectangle (2x2) 析构.
Rectangle (7x3) 析构.
Rectangle (10x5) 析构.

这个输出完美地展示了:

  • 即使我们调用的是两参数或三参数的构造函数,四参数的目标构造函数总是先被执行。
  • 然后,相应的委托构造函数的函数体才被执行。
  • 对象的成员变量确实是根据我们提供的参数和默认值被正确初始化的。

8. 建议和扩展问题

建议:

  1. 保持一致性: 当你为委托构造函数提供默认值时,确保这些值与你的设计意图和类的整体行为一致。比如例子中的颜色字符串,”Red” 和 “red” 虽然在这里效果一样,但在大小写敏感的比较中会有差异。保持一种风格(比如都用首字母大写)会更好。
  2. 明确目标构造函数: 在设计具有多个构造函数的类时,可以考虑将一个功能最全、能初始化所有核心成员的构造函数作为主要的“目标构造函数”。其他构造函数则通过委托并提供默认值来调用它。
  3. 善用 const 和引用: 对于像 std::string 这样的类型,如果只是读取它们的值,在参数传递时使用 const std::string& 通常比传值 std::string 更高效,可以避免不必要的拷贝。你的目标构造函数已经这样做了,非常好!

扩展问题供你思考和研究:

  1. 委托链 (Delegation Chain): 一个构造函数 A 可以委托给构造函数 B,构造函数 B 能否再委托给构造函数 C?(提示:可以,但要小心不要形成循环委托。)如果可以,执行顺序是怎样的?
  2. 构造函数委托与默认参数 (Default Arguments): 在某些情况下,你可能发现使用构造函数的默认参数也能达到类似减少代码重复的效果。例如:
    cpp
    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Gadget {
    int id;
    std::string name;
    public:
    Gadget(int i, const std::string& n = "DefaultGadget") : id(i), name(n) {
    std::cout << "Gadget(" << id << ", " << name << ") constructed.\n";
    }
    // Gadget(int i) : Gadget(i, "DefaultGadget") {} // 也可以用委托
    };
    构造函数委托和默认参数在哪些场景下各有优势?什么时候你会倾向于使用其中一种而不是另一种?(提示:考虑当默认值的确定逻辑比较复杂,或者你想在不同的“入口”构造函数体中执行不同逻辑时。)
  3. 构造函数委托与 explicit 关键字: explicit 关键字用于防止单参数构造函数的隐式类型转换。它与构造函数委托如何相互作用?一个 explicit 的构造函数可以委托给另一个构造函数吗?一个构造函数可以委托给一个 explicit 的构造函数吗?
  4. 错误处理: 如果在目标构造函数中(或者它调用的任何函数中)抛出异常 (exception),委托链会如何表现?委托构造函数的函数体还会执行吗?

这些问题可以帮助你更深入地理解构造函数委托及其在C++类设计中的应用。

你做得非常棒!继续努力,C++的世界还有很多奇妙的特性等着你去探索!如果你有任何其他问题,随时问我。


评论