【问题标题】:Constructor parameter style构造函数参数样式
【发布时间】:2013-06-01 10:27:34
【问题描述】:

假设我的文件如下所示:

#include <iostream>
#include <string>

using namespace std;

class testclass{
public: string name;
        //testclass(const string& sref){
          //  name = sref;
        //}
        testclass(string str){
            name = str;
        }
        ~testclass(){}
};

int main(){
    testclass t1("constStringRef");
    cout << t1.name << '\n';
}

给定以下构造函数调用,构造函数 1 和 2 有什么区别:

testclass tobj("tmemberstring");

这是我的想法:

我知道通过引用传递意味着您不传递副本,但由于字符串参数,首先有一个字符串初始化(在这两种情况下都被视为局部变量,我假设),然后然后是在案例 1 中初始化对它的引用,或者在案例 2 中复制到新字符串 str。最后,两个构造函数都将值复制到成员字符串名称。如果我的想法是正确的,我会跳过一个步骤(复制到字符串 str)如果会使用第一个构造函数。

附加问题: 参数是否存储在堆栈区域中? 如果是这样,这个特定的字符串引用或对任何基本数据类型的引用将使用多少空间?

希望得到您的建议, 提前致谢

【问题讨论】:

  • 代码中的所有内容都毫无意义。 ns 是什么?!
  • 在将代码膨胀到 StackOverflow 之前,您应该尝试编译代码!
  • 抱歉重命名以使其更清晰,忘记重命名分配。
  • 两者都不做;如果您要在类中存储字符串的副本,请按值接受参数,然后将std::move在构造函数的初始化列表中

标签: c++ methods parameters reference constructor


【解决方案1】:

回答您的问题的最简单方法是分解两种情况下发生的情况。

testclass(const string&amp; sref)

  • testclass t1("constStringRef"); 首先从const char* 创建一个临时的string 对象
  • 构造函数被调用,临时string对象绑定到构造函数的const string&amp;参数
  • name 是无用的默认构造,因为您没有使用构造函数的初始化列表(稍后会详细介绍
  • 调用string::operator =,复制const string&amp; 参数

总计: 1 份。

testclass(string str)

  • testclass t1("constStringRef"); 首先从const char* 创建一个临时的string 对象
  • 调用了构造函数——发生的情况取决于您使用的 C++ 版本:
    • C++03:临时string 对象被复制到构造函数的参数中
    • C++11:临时被移动到构造函数的参数中
  • name 是无用的默认构造,因为您没有使用构造函数的初始化列表
  • 调用string::operator =,复制string 参数

总计:C++03 2 份,C++11 1 份。


由此,我们可以相信const string&amp; 更好。然而这仅适用于 C++03


C++11 和移动语义

在 C++11 中,最好(在这种情况下)将字符串按值传递给构造函数,然后将参数移动到您的类成员中:

    testclass(string str){
        name = std::move(str);
    }

让我们看看现在会发生什么:

  • testclass t1("constStringRef"); 首先从const char* 创建一个临时的string 对象
  • 构造函数被调用,临时被移动到构造函数的参数中
  • name 是无用的默认构造,因为您没有使用构造函数的初始化列表
  • string::operator = 被调用,但这次 移动 string 参数到 name

总计:0份!


对于右值来说这一切都很好,但是对于左值来说这仍然适用吗?

string s = "..."; // s has already been constructed some time ago
testclass t1(s);  // what happens during this call?
  • 对于采用 const string&amp; 的构造函数(在 C++03 和 C++11 中):

    • s 绑定到 const string&amp; 参数
    • name 是无用的默认构造,因为您没有使用构造函数的初始化列表
    • 调用string::operator =,复制const string&amp; 参数
    • 总计: 1 份。
  • 对于接受 string 然后移动它的构造函数(仅在 C++11 中):

    • s 被复制到 string 参数中
    • name 是无用的默认构造,因为您没有使用构造函数的初始化列表
    • string::operator = 被调用,但这次 移动 string 参数到 name
    • 总计: 1 份。

总结

在 C++03 中,无论您传递的是左值还是右值都无关紧要,使用const string&amp; 总是更有效。正如其他人所提到的,您可能希望重载构造函数以采用 const char* 参数,从而避免无用的副本。

在 C++11 中,如果您将参数移动到成员变量中,string 参数与左值的 const string&amp; 参数相同,但对于右值更有效(根本不需要进行复制)。所以你应该使用传值,然后将参数移动到成员变量中。


最后但同样重要的是,您注意到我坚持无用的默认构造 name。为避免这种情况,请使用构造函数的初始化列表而不是构造函数主体中的赋值:

    // C++03
    testclass(const char* str) : name(str) {}       // no copy
    testclass(const string& sref) : name(sref) {}   // 1 copy

    // C++11
    testclass(string str) : name(std::move(str)) {} // 1 copy for lvalues,
                                                    // no copy for rvalues

【讨论】:

    【解决方案2】:

    在这两种情况下,构造函数都接受std::string。由于您使用字符串文字(const char*)调用构造函数,因此将构造一个临时的std::string 以调用构造函数。这两种方法的区别在于接下来会发生什么:

    testclass(const string&amp; sref) 的情况下,可以使用 const 对您刚刚创建的临时 string 的引用。在第二种情况下,字符串是按值取值的,因此需要创建一个 second 临时+。

    • 注意:编译器有时可以优化第二个临时的。

    作为一般经验法则,我建议尽可能使用const&amp;

    但是请注意,您可以完全避免构造临时的 std::string,只需通过模板接受字符串文字即可:

    template <size_t N> testclass(const char (&str)[N])
    {
      name = str;
    }
    

    还要注意,当你的构造函数被调用时,会发生两件事。 1) name 成员被构造。 2) name 成员的值发生了变化。您可以使用初始化列表在一个步骤中初始化和构造 name 成员:

    template <size_t N> testclass(const char (&str)[N])
    : 
      name (str, N-1)  // minus one to not copy the trailing `\0`.  Optional, depending
    {
      name = str;
    }
    

    【讨论】:

    • template &lt;size_t N&gt; testclass(const char* (&amp;str)[N]) => 这是完全错误的。在这里,您将使用 array[N]const char*。要么只用const char*,要么用const char (&amp;str)[N]。显然,最好只使用const char*,因为数组可以衰减为指针,而指针不能提升为固定长度的数组。还有,为什么要初始化+赋值?
    • @syam:感谢您指出这一点。这就是我不编译的结果。
    【解决方案3】:

    首先,最好用“std::endl”而不是“'\n'”来完成流线。 一个构造函数需要一个引用,另一个是一个值,但是您正在传递一个值为“const char *”的 C 字符串。 第三:在调用构造函数代码之前初始化你的成员。 我会推荐以下内容;

    testclass(const char* name) : name(name) {};
    

    【讨论】:

    • cout 通常是行缓冲的,在这种情况下,endl\n 没有任何区别。
    • @Praetorian:但是,为什么强制同花是更好的建议?
    • 是的,我知道我传递了一个 c 字符串,但由于字符串的转换构造函数,这应该无关紧要。如果我错了,请纠正我,并感谢您提供有关初始化的信息。
    【解决方案4】:

    我知道通过引用传递意味着你不传递副本

    没错,所以你通常应该更喜欢testclass::testclass(const std::string&amp;),因为它避免了那个副本

    但由于字符串参数,首先有一个字符串初始化

    是的,要么创建了一个临时字符串并作为 const ref 传递,要么直接创建了参数。

    还有另一个字符串:您的 name 成员默认初始化只是因为您没有使用初始化列表。这个:

    testclass::testclass(const std::string &s) : name(s) {}
    

    初始化name直接,而不是先默认初始化它,然后在构造函数主体中更改它。


    附加问题:参数是否存储在堆栈区域中?

    参数可以保存在堆栈中,或寄存器中,或以编译器发现符合标准的任何其他方式。这是一个实现细节。

    ...如果是这样,这个特定的字符串引用或对任何基本数据类型的引用将使用多少空间?

    一般来说,引用可能最多是指针的大小(并且可能被完全省略)。

    您没有考虑到的是std::string 拥有一个动态分配的字符数组副本,因此您通过值传递的每个std::string 都可能执行动态分配,然后在执行时取消分配超出范围。 (一些编译器可能会通过引用计数来避免这种情况,但我们会回到实现细节上)。

    【讨论】:

      猜你喜欢
      • 2023-03-17
      • 1970-01-01
      • 2013-12-13
      • 1970-01-01
      • 1970-01-01
      • 2011-10-27
      • 2020-02-13
      • 1970-01-01
      相关资源
      最近更新 更多