17

I am not sure that I am using the right terminology, but question is how do I properly make a constructor that takes a string in as a parameter?

I am used to having a const char * in the constructor instead of strings.

Normally I would do something like this:

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;
}

What if the constructor is this:

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

Do I still do base member initialization? do I still need to use string copy in the base of the constructor?

Scott Mermelstein
  • 15,174
  • 4
  • 48
  • 76
Sarah
  • 2,713
  • 13
  • 31
  • 45

4 Answers4

15

Use std::string and initializer lists:

std::string fName, lName;

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

In this case, you don't need to use terribly bare pointers, you don't need allocate memory, copy characters and finally de-allocate. In addition, this new code has chances to take advantages of moving rather than copying since std::string is movable. Also it's useful to read this.

And so on....

Community
  • 1
  • 1
masoud
  • 55,379
  • 16
  • 141
  • 208
  • Why wouldn't I initialize to 0 as I would with char *? – Sarah Nov 05 '13 at 23:26
  • @Sarah: Because std::string objects are not supposed to be 0, because it are objects (not pointers). They are empty, like in the sense of `""`. – Martijn Courteaux Nov 05 '13 at 23:28
  • 1
    @Sarah: In addition to Martijn's comment, above code is initializing and copying simultaneously. You don't need anything else. – masoud Nov 05 '13 at 23:30
  • The above code could actually incur at least 2 copies unless you explicitly specify `std::move` in the initializer list. Also, this is not a good example of move semantics because you still only get 1 copy; exactly the same as if you had used a reference. – RamblingMad Jun 30 '15 at 04:19
  • @CoffeeandCode: Yes, you are right, I don't know why I forgot to use `std::move` – masoud Jun 30 '15 at 06:27
14

I see that you have already accepted an answer but I would like to expand upon the answers.

As deepmax said, if you pass by value you can write your constructor to take advantage of "move semantics". This means instead of copying data, it can be moved from one variable to another.

Written like so:

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

        std::string mem_var;
};

Which seems like a good idea, but in reality is no more efficient than the copy constructor

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

        std::string mem_var;
};

The reason this is, is because in the general use case that looks like this:

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

only one copy will ever get made either way (see copy elision), and in the other case of

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

2 copies will be made in the 'efficient' pass-by-value move semantics way!

This is a good example of when the copy constructor (or pass-by-reference) should be used, not an example against it.


On the contrary...

If you write an explicit constructor that makes use of move semantics you could get an efficient solution no matter the circumstance.

Here is how you might write out a name class definition with both constructors:

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;
};

Then when you use the class the more efficient path should be taken.

If we go back to our examples we can rewrite them to make use of the best or most efficient constructor:

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));
}

Never just take for granted that one solution is better than all others!

RamblingMad
  • 5,332
  • 2
  • 24
  • 48
  • I see what you did there – Maitreya Apr 09 '21 at 21:45
  • Where are two copies in case of pass-by-value constructor and ``` auto main() -> int{ std::string myname = "Hugh Jaynus"; Name name(myname); } ```? One copy is from `myname` variable to constructor argument, which is then moved to the class member. Also consider this article which explains the topic better than I could do in a comment: http://cpptruths.blogspot.com/2012/03/rvalue-references-in-constructor-when.html – Alexandr Zarubkin Oct 19 '22 at 21:54
3

I'm used to do this:

std::string fName;
std::string lName;

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

Using the references saves the work of copying the strings to a new object on the stack, it will just pass the reference to the existing string. Once you are assigning them to the class members, they will get copied.

Martijn Courteaux
  • 67,591
  • 47
  • 198
  • 287
  • Why you don't pass them by value if you concern efficiency? – masoud Nov 05 '13 at 23:22
  • but what if I'm not using references to strings in my parameters? – Sarah Nov 05 '13 at 23:25
  • @MM: I wrote that in my answer: because they won't get copied by passing them to the constructor. The constructor will have to copy them as well, in every case (with or without references). – Martijn Courteaux Nov 05 '13 at 23:26
  • @Sarah: Well, then you either stick with your current situation or change it to const references. – Martijn Courteaux Nov 05 '13 at 23:26
  • @MartijnCourteaux: Move semantics (C++11) say something else, read [this article](http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/). – masoud Nov 05 '13 at 23:28
  • @MM.: I read the article, and if I understood correctly, it only gives performance gains in specific situation. This case does not apply, I think? The final copy from the method argument to the class member can never be avoided right? When using references, and doing this: `new Name(getSomeString(), getSomeString())`, we would have two copies per argument: 1) from getSomeString() to the callee, 2) from the callee to the class member. If we did the same, using pass by value, we would only have one copy: from getSomeString() to the class member. Did I understand correctly? – Martijn Courteaux Nov 05 '13 at 23:51
  • If you pass by value, in case of temporary passed objects (e.g. `Name name("Martijn","Courteaux");`) it will _move_ the values and you have no copy. In case of non-temporary passed objects (e.g. `Name name(fn, ln);`) you have one copy as same as passing by reference. Summery: Passing by value can avoid unnecessary copies for temp objects. Therefore, it's logical to prefer it. – masoud Nov 05 '13 at 23:58
  • I understand that. But that seems logical to me, because the create `Name name` object is on the stack, so the compiler can just put it immediately at the right position. But I was talking about a `new`-ly created object (on the heap). – Martijn Courteaux Nov 06 '13 at 00:01
  • So, to ask in a clear way: When create a `new Name("Martijn", "Courteaux");` (`new`, so on the heap). It wouldn't make any difference if we did pass by value or by reference, right? It would have to copy both times once? – Martijn Courteaux Nov 06 '13 at 00:05
  • It doesn't depend on `Name` is on stack or heap. Pass by value has the chance to move those names to class members instead of copying. – masoud Nov 09 '13 at 10:35
0

if you want to keep const char * as your constructor input types do this.

std::string fName;
std::string lName;

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

You can construct a std::string from a const char.