C++类组合 - 构造函数和析构函数何时被执行

4

请帮忙!我知道这个问题很长,但我真的没有人可以帮助我理解这个概念。

我有一本书上的代码,但我真的完全不理解输出。这段代码是关于两个类合成的例子,以及构造函数和析构函数执行的顺序。

对于这个冗长的例子,我感到抱歉:

// Fig. 10.8: Date.h
// Date class definition; Member functions defined in Date.cpp
#ifndef DATE_H
#define DATE_H

class Date
{
    public:
    static const unsigned int monthsPerYear = 12; // number of months in a year
    explicit Date( int = 1, int = 1, int = 1900 ); // default constructor
    void print() const; // print date in month/day/year format
    ~Date(); // provided to confirm destruction order

private:
    unsigned int month; // 1-12 (January-December)
    unsigned int day; // 1-31 based on month
    unsigned int year; // any year

    // utility function to check if day is proper for month and year
    unsigned int checkDay( int ) const;
}; // end class Date

#endif

// Fig. 10.9: Date.cpp
// Date class member-function definitions.
#include <array>
#include <iostream>
#include <stdexcept>
#include "Date.h" // include Date class definition
using namespace std;

// constructor confirms proper value for month; calls
// utility function checkDay to confirm proper value for day
Date::Date( int mn, int dy, int yr )
{
    if ( mn > 0 && mn <= monthsPerYear ) // validate the month
        month = mn;
    else
        throw invalid_argument( "month must be 1-12" );

    year = yr; // could validate yr
    day = checkDay( dy ); // validate the day

    // output Date object to show when its constructor is called
    cout << "Date object constructor for date ";
    print();
    cout << endl;
} // end Date constructor

    // print Date object in form month/day/year
void Date::print() const
{
    cout << month << '/' << day << '/' << year;
} // end function print

// output Date object to show when its destructor is called

Date::~Date()
{
    cout << "Date object destructor for date ";
    print();
    cout << endl;
} // end ~Date destructor

// utility function to confirm proper day value based on // month and     year; handles leap years, too
unsigned int Date::checkDay( int testDay ) const
{
    static const array < int, monthsPerYear + 1 > daysPerMonth =
        { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

    // determine whether testDay is valid for specified month
    if ( testDay > 0 && testDay <= daysPerMonth[ month ] )
        return testDay;

    // February 29 check for leap year
    if ( month == 2 && testDay == 29 && ( year % 400 == 0 || ( year % 4 == 0 && year % 100 != 0 ) ) )
        return testDay;

    throw invalid_argument( "Invalid day for current month and year" );
} // end function checkDay

// Fig. 10.10: Employee.h
// Employee class definition showing composition. // Member functions     defined in Employee.cpp.
#ifndef EMPLOYEE_H
#define EMPLOYEE_H

#include <string>
//#include "Date.h" // include Date class definition using namespace std;
class Employee
{
public:
    Employee( const string &, const string &, const Date &, const Date & );
    void print() const;
    ~Employee(); // provided to confirm destruction order
private:
    string firstName; // composition: member object
    string lastName; // composition: member object
    const Date birthDate; // composition: member object
    const Date hireDate; // composition: member object 23
}; // end class Employee

#endif

// Fig. 10.11: Employee.cpp
// Employee class member-function definitions.
#include <iostream>
#include "Employee.h" // Employee class definition
#include "Date.h" // Date class definition
using namespace std;

// constructor uses member initializer list to pass initializer
// values to constructors of member objects
Employee::Employee( const string &first, const string &last, const Date &dateOfBirth, const Date &dateOfHire )
: firstName( first ), lastName( last ), birthDate( dateOfBirth ), hireDate( dateOfHire )
{
    // output Employee object to show when constructor is called
    cout << "Employee object constructor: " << firstName << ' ' << lastName << endl;
} // end Employee constructor

// print Employee object
void Employee::print() const
{
    cout << lastName << ", " << firstName << " Hired: ";
    hireDate.print();

    cout << " Birthday: ";
    birthDate.print();

    cout << endl;
} // end function print

// output Employee object to show when its destructor is called
Employee::~Employee()
{
    cout << "Employee object destructor: " << lastName << ", " << firstName << endl;
} // end ~Employee destructor

// Fig. 10.12: fig10_12.cpp
// Demonstrating composition--an object with member objects.
#include <iostream>
//#include "Employee.h" // Employee class definition
using namespace std;

int main()
{
    Date birth( 7, 24, 1949 );
    Date hire( 3, 12, 1988 );
    Employee manager( "Bob", "Blue", birth, hire );

    cout << endl;
    manager.print();
}//endmain

我知道这段代码太长了,但是真的想不到更简单的方法来完成这个任务。以下是输出结果。

Date object constructor for date 7/24/1949
Date object constructor for date 3/12/1988
Employee object constructor: Bob Blue

Blue, Bob  Hired: 3/12/1988  Birthday: 7/24/1949
Employee object destructor: Blue, Bob
Date object destructor for date 3/12/1988
Date object destructor for date 7/24/1949
Date object destructor for date 3/12/1988
Date object destructor for date 7/24/1949

现在,我的问题是... 为什么Date对象的构造函数只执行了两次,而析构函数执行了四次?据我理解,Date对象的构造函数也应该执行四次: 1. 在main中的 'Date birth(7, 24, 1949)' 2. 在main中的 'Date hire(3, 12, 1988)' 3. 在Employee.h中的 'const Date birthDate' 4. 在Employee.h中的 'const Date hireDate'
#3和#4的原因是我无法想象Employee对象构造函数如何复制'birth'和'hire',而没有相同类型的空变量(或在这种情况下的Date对象)。 因此,我理解在Employee头文件中,当创建私有成员Date对象时,应显式执行默认的Date构造函数(对于每个Date对象,其成员变量mn、dy、yr均为1/1/1900)。

第三和第四个案例使用了Date的复制构造函数,其中没有任何cout语句。显然它不能使用Date(int mn, int dy, int yr),因为您从未编写过任何代码来调用带参数的构造函数。 - M.M
1
如果你将 birthDate( dateOfBirth ), 改为 birthDate( dateOfBirth.month, dateOfBirth.day, dateOfBirth.year ),,它将使用该构造函数。 - M.M
我还没有完全理解复制构造函数的概念。但是根据您的评论,我会认为成员变量的定义和初始化并不一定会创建具有相同类型的空变量,我是正确的吗? 或者说,它确实会通过不同的构造函数(称为复制构造函数)创建具有相同类型的空变量吗? 如果是这样,那么像字符串和整数这样的非类变量呢?它们是否以不同的方式创建,还是因为它们不是类,所以执行普通声明即可? - GrinNare
不存在“空变量”这样的东西。类类型的未初始化变量必须通过构造函数创建(std::string是一个类类型)。原始类型可以采用不确定值,这意味着它们必须在使用之前被赋值。 - M.M
当你写下 birthdate( dateOfBirth ) 时,它的意思是“使用接受参数 dateOfBirth 的构造函数创建 birthdate”。这种类型的构造函数被称为复制构造函数。 - M.M
1个回答

3
额外的两个 Date 析构函数调用是由于 Employee 拥有其中的两个实例。
当一个 Employee 被销毁时,这些实例也会被销毁。
另外,你只记录了 Date(int,int,int) 构造函数。编译器为 Date 提供了一个拷贝构造函数,该函数在使用 const Date & 初始化 Employee 的 2 个 Date 成员时使用。
编辑:
如果您想记录拷贝构造函数的调用,则需要自己定义拷贝构造函数:
class Date
{
    unsigned month;
    unsigned day;
    unsigned year;

public:

    // Other stuff here

    explicit Date(int, int, int);

    Date(const Date &other) :
    //
    // Since this is a user-defined copy constructor,
    // all member initialization must be done explicitly.
    //
    month{other.month},
    day{other.day},
    year{other.year}
    {
        cout << "Date copy constructor" << endl;
    }

    // Rest of stuff here
};

谢谢您的回答。我想我还没有理解复制构造函数的概念。所以,成员对象的定义和初始化会创建一个空对象,其类型相同,但是通过不同的构造函数(称为复制构造函数)来实现?如果是这样,那么像字符串和整数这样的非类变量呢?它们是否以不同的方式创建,或者由于它们不是类,因此执行正常声明? - GrinNare
隐式复制构造函数(由编译器提供的那个)只是使用它们自己的复制构造函数初始化所有成员,而不考虑类型。关于编译器实际上可以生成隐式构造函数的位置还有其他规则,但我现在会跳过这些细节。 - defube
等等!还有一件事。我知道当一个对象被复制时,会使用拷贝构造函数。但是,出生日期和雇用日期不是在头文件中创建然后再被复制吗? 既然 Date 对象是先“创建”而不是“复制”,那么不应该使用用户定义的显式构造函数吗? - GrinNare
在文件头中,它们仅被声明:您已将它们描述为结构的成员。只有在构造函数中,成员才会被初始化。构造函数在实例被声明时被调用。 - defube
你的意思是无论在哪里实例被“初始化”了吗?请确保是“初始化”。如果是这样,事情就清楚了。 - GrinNare
是的。除非成员是“静态”的,否则它只会在调用构造函数时初始化。 - defube

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接