构造函数参数样式

Constructor parameter style

本文关键字:样式 参数 构造函数      更新时间:2023-10-16

假设我的文件是这样的:

#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),它将使用第一个构造函数。

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

希望你的建议,提前致谢

回答你的问题最简单的方法是把这两种情况分别分析一下。

testclass(const string& sref)

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

Total: 1 copy.

testclass(string str)

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

    在c++ 03副本2份,1份c++ 11。


    由此,我们可以认为const string&是更好的。但是这只适用于c++ 03


    c++ 11和移动语义

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

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

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

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

    Total: 0 copy!


    这对于右值来说都很好,但是对于左值来说仍然成立吗?

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

      • s被绑定到const string&参数
      • name是无用的默认构造,因为你没有使用构造函数的初始化列表
      • 调用
      • string::operator =,复制const string&参数
      • Total: 1 copy.
    • 用于获取string然后移动它的构造函数(仅在c++ 11中):

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

    结束

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

    在c++ 11中,如果您将参数移动到成员变量中,则string参数与const string&参数对于左值相同,但对于右值更有效(根本不需要执行复制)。因此,您应该使用按值传递,然后将参数移动到成员变量。


    最后但并非最不重要的是,您注意到我坚持使用无用的默认构造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
    

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

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

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

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

    但是请注意,您可以完全避免构造临时的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 ``.  Optional, depending
    {
      name = str;
    }
    

    首先,最好以"std::endl"而不是"'n'"结束您的流行。一个构造函数需要一个引用,另一个需要一个值,但是你传递的是一个值为"const char*"的C-String。第三:在调用构造函数代码之前初始化成员。我推荐以下内容:

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

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

    是的,所以你通常应该选择testclass::testclass(const std::string&),因为它避免了复制

    但是由于string参数首先有一个string初始化

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

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

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

    直接初始化name ,不需要先默认初始化它,然后在构造函数体中修改它。


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

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

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

    一般来说,引用可能不超过指针的大小(并且可能完全省略)。

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