【问题标题】:Use of ":" symbol to define a constructor in C++ [duplicate]使用“:”符号在 C++ 中定义构造函数 [重复]
【发布时间】:2014-06-23 00:02:38
【问题描述】:

我正在从结构化 C 迁移到 OOP C++,并且在 C++ 中声明/定义构造函数时,我经常发现“:”符号作为运算符的特殊用途。我大致了解了这种风格的用法,但有人用这个构造函数定义向我解释了确切的编程技术。

例如:1

class time_stamp
{
public:
    time_stamp(time &t_time)
        : m_time(t_time)
    {}

    ~time_stamp()
    {
        m_time.update(); // as soon as I'm destroyed, update the time
    }
private:
    time &m_time;
};

例如:2

class threaded_class
{
public:
    threaded_class()
        : m_stoprequested(false), m_running(false)
    {
        pthread_mutex_init(&m_mutex);
    }

    ~threaded_class()
    {
        pthread_mutex_destroy(&m_mutex);
    }

    /** Some other member declarations */

}

请解释我在上面 2 个示例的以下代码行中使用“:time_stamp(time &t_time) : m_time(t_time){}

threaded_class(): m_stoprequested(false), m_running(false)
{
   pthread_mutex_init(&m_mutex);
}

【问题讨论】:

标签: c++ oop


【解决方案1】:

冒号: 用于表示构造函数成员初始化列表。这是您可以初始化类成员或调用基类构造函数的地方。

C++ 标准 n3337 12.6.2 § 3:

mem-initializer-list 可以使用任何 class-or-decltype 表示基类类型。

C++ 标准 n3337 12.6.2 § 7:

使用了 mem-initializer 中的表达式列表或大括号初始化列表 初始化指定的子对象(或者,在 委托构造函数,完整的类对象)根据 8.5 直接初始化的初始化规则。

例子:

class Foo {
   int a;
};

如果您希望整数 a 在调用构造函数后具有确定的值,则必须在构造函数中给出 a 这个值。有两种选择:

  • 在构造函数体中

    Foo::Foo() {
        a = 70;
    }
    
  • 在它的成员初始化列表中

    Foo::Foo() : a( 70) {
    }
    

应该首选通过成员初始化列表进行初始化

它总是合法,永远不会比构造函数体内的赋值效率低,而且通常更有效。关于初始化列表的非常重要的事情是它允许直接初始化类成员,而忽略了受此类过程影响的成员的默认构造。

正如 Scott Myers 在他的“Effective C++”中指出的那样,如果你没有为类成员指定一个初始化参数,它的默认构造函数将被调用。当您稍后在类构造函数中对其执行赋值时,您将在成员变量上调用 operator=。这将总共调用两个成员函数:一个用于默认构造函数,另一个用于赋值。您可以通过指定初始化程序来省略第一次调用。同样正如 Scott Myers 在他的“Effective C++”中指出的那样:“从纯粹实用的角度来看,有时必须使用初始化列表。特别是,const 和引用成员只能被初始化,而不是被分配” .

陷阱

(至少)同样重要的是,成员不是按照它们在初始化列表中出现的顺序进行初始化,而是按照在类中的声明顺序进行初始化。记住这一点以避免像

这样的错误
/* trying to allocate very large block of memory
   as a result of initializing a vector with
   uninitialized integer: std::vector<int> v( N)
*/
class SearchEngine {
    std::vector<int> v;
    int N;
    explicit SearchEngine( std::vector<int> const& keys)
                  : N( keys.size()), v( N), {

C++ 标准 n3337 8.5.4 § 1:

List-initialization 是一个对象或引用的初始化 支撑初始化列表。这样的初始化器称为初始化器列表, 列表中以逗号分隔的初始化子句称为 初始化列表的元素。初始化列表可能为空。 列表初始化可以发生在直接初始化或复制中 初始化上下文;列表初始化 直接初始化上下文称为直接列表初始化和 复制初始化上下文中的列表初始化称为 复制列表初始化。 [注意:可以使用列表初始化——作为 变量定义中的初始化器 (8.5)

——作为初始化器 一个新的表达式(5.3.4)

——在 return 语句中 (6.6.3)

——作为 函数参数(5.2.2)

——作为下标(5.2.1)

——作为参数 构造函数调用 (8.5, 5.2.3)

——作为 a 的初始化器 非静态数据成员(9.2)

——在内存初始化器(12.6.2)中

——在 作业的右侧 (5.17)

[ 示例:

int a = {1};

std::complex z{1,2};

新的 std::vector{"once", “曾几何时”}; // 4 个字符串元素

f( {"尼古拉斯","安妮玛丽"} ); // 传递两个元素的列表

返回 {“诺拉”}; // 返回列表 一个元素

int* e {}; // 初始化为零/空指针

x = 双{1}; // 显式构造一个 double

std::map 动画 = { {"熊",4}, {"食火鸡",2}, {"老虎",7} };

—结束示例]—结束注释]

【讨论】:

  • 感谢您的解释,我参考了 C11++ 标准中提到的部分以了解更多详细信息。这意味着我必须使用: memberName (value) 样式才能从中受益。
【解决方案2】:

初始化列表。当您想在构造后立即初始化成员对象时,它很有用。当成员对象没有默认构造函数时,您必须使用它。

而且它不仅仅是初始化成员的另一种方式,有时你必须使用它,而且大多数时候你应该使用它来保持代码的一致性。

这是一个你必须使用它的情况的例子:

struct A
{
   const X x; // X has not default constructor

   A() : x(some_value) {}
};

即使成员对象有默认构造函数,也应该通过initialization-list进行初始化,避免重复构造。

struct A
{
   string x;

   A() : x("Hello") {}
};

在上述情况下,如果您在构造函数的主体内分配"Hello",那么您对string::string()x = "Hello"; 进行了不必要的调用,只需调用string::string("Hello") 即可替换它。

【讨论】:

    【解决方案3】:

    用于成员初始化。这是唯一可以在不默认初始化成员的情况下初始化成员的地方。

    如果您在花括号中执行此操作,则成员的默认构造函数已经被调用并且您为其分配了新值。使用冒号语法,您可以决定如何初始化成员(根据普通类型的值和非普通类型的构造函数)。

    【讨论】:

      猜你喜欢
      • 2019-04-14
      • 2015-09-13
      • 1970-01-01
      • 2018-04-13
      • 2014-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多