【问题标题】:How to initialize a constructor with that takes Strings as parameters?如何初始化一个以字符串为参数的构造函数?
【发布时间】:2013-11-17 00:46:37
【问题描述】:

我不确定我使用的术语是否正确,但问题是我如何正确地创建一个将字符串作为参数的构造函数?

我习惯于在构造函数中使用const char * 而不是字符串。

通常我会这样做:

Name(const char* fName, const char* lName)
    : firstName(0), lastName(0)
{
    char * temp = new char [strlen(fName) + 1];
    strcpy_s(temp, strlen(fName) + 1, fName);
    firstName = temp;

    char * temp2 = new char [strlen(lName) + 1];
    strcpy_s(temp2, strlen(lName) + 1, lName);
    lastName = temp2;
}

如果构造函数是这样的:

 Name(const string fName, const string lName) { }

我还要进行基成员初始化吗?我还需要在构造函数的基础中使用字符串复制吗?

【问题讨论】:

    标签: c++ string constructor


    【解决方案1】:

    我看到您已经接受了一个答案,但我想扩展答案。

    正如 deepmax 所说,如果您按值传递,则可以编写构造函数以利用“移动语义”。这意味着可以将数据从一个变量移动到另一个变量,而不是复制数据。

    这样写:

    class Name{
        public:
            Name(std::string var): mem_var(std::move(var)){}
    
            std::string mem_var;
    };
    

    这似乎是个好主意,但实际上并不比复制构造函数更有效

    class Name{
        public:
            Name(const std::string &var): mem_var(var){}
    
            std::string mem_var;
    };
    

    之所以这样,是因为在一般用例中看起来像这样:

    auto main() -> int{
        Name name("Sample Text");
    }
    

    无论哪种方式都只会制作一个副本(请参阅copy elision),而在另一种情况下

    auto main() -> int{
        std::string myname = "Hugh Jaynus";
        Name name(myname);
    }
    

    将以“高效”传递值移动语义的方式制作 2 个副本!

    这是一个很好的例子,说明何时应该使用复制构造函数(或传递引用),而不是反对它的例子。


    恰恰相反……

    如果您编写一个使用移动语义的显式构造函数,无论在何种情况下,您都可以获得有效的解决方案。

    以下是您可以如何使用两个构造函数编写名称类定义:

    class Name{
        public:
            Name(const std::string &first_, const std::string &last_)
                : first(first_), last(last_){}
    
            Name(std::string &&first_, std::string &&last_) // rvalue reference
                : first(std::move(first_)), last(std::move(last_)){}
    
            std::string first, last;
    };
    

    那么当你使用类时,应该采取更有效的路径。

    如果我们回到我们的示例,我们可以重写它们以使用最好或最有效的构造函数:

    int main(){
        // pass by reference best here
        Name myname("Yolo", "Swaggins");
    
        // move most efficient here
        // but never use 'first' and 'last' again or UB!
        std::string first = "Hugh", last = "Jaynus";
        Name yourname(std::move(first), std::move(last));
    }
    

    永远不要想当然地认为一种解决方案优于其他所有解决方案!

    【讨论】:

    • 我看到你在那里做了什么?
    【解决方案2】:

    使用std::string 和初始化列表:

    std::string fName, lName;
    
    Name(string fName, string lName):fName(std::move(fName)), lName(std::move(lName))
    {
    }
    

    在这种情况下,您不需要使用非常裸露的指针,不需要分配内存、复制字符和最后取消分配。此外,由于std::string 是可移动的,因此这个新代码有机会利用移动而不是复制的优势。阅读this也很有用。

    等等……

    【讨论】:

    • 为什么我不能像使用 char * 那样初始化为 0?
    • @Sarah:因为 std::string 对象不应该为 0,因为它是对象(不是指针)。它们是空的,就像"" 一样。
    • @Sarah:除了Martijn的评论,上面的代码是同时初始化和复制的。你不需要其他任何东西。
    • 除非你在初始化列表中明确指定std::move,否则上面的代码实际上至少会产生2个副本。此外,这不是移动语义的一个很好的例子,因为你仍然只能得到 1 个副本;就像您使用了参考一样。
    • @CoffeeandCode:是的,你说得对,我不知道为什么我忘了用std::move
    【解决方案3】:

    如果您想保留 const char * 作为构造函数的输入类型,请执行此操作。

    std::string fName;
    std::string lName;
    
    Name(const char *_fName, const char *_lName) :
         fName(_fName), lName(_lName)
    {
    }
    

    您可以从 const char 构造 std::string。

    【讨论】:

    • 以下划线开头的标识符是编译器保留的,你应该把它们放在后面。
    【解决方案4】:

    我习惯这样做:

    std::string fName;
    std::string lName;
    
    Name(const std::string &fName, const std::string &lName) :
         fName(fName), lName(lName)
    {
    }
    

    使用引用节省了将字符串复制到堆栈上的新对象的工作,它只会将引用传递给现有字符串。将它们分配给班级成员后,它们将被复制。

    【讨论】:

    • 如果你关心效率,为什么不按值传递?
    • 但是如果我没有在参数中使用对字符串的引用呢?
    • @MM:我在回答中写道:因为它们不会通过将它们传递给构造函数来被复制。在每种情况下(有或没有引用),构造函数也必须复制它们。
    • @Sarah:好吧,那么你要么坚持当前的情况,要么将其更改为 const 引用。
    • @MartijnCourteaux:移动语义 (C++11) 说点别的,阅读this article
    猜你喜欢
    • 2011-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-19
    相关资源
    最近更新 更多