0

I am really confused how compiler allocates STL objects. Consider the following code:

#include <string>

using namespace std ;

class s { 
    public:
    string k ; 

    s(string k) : k(k) {}
} ;

void x ( s obj ) {
    string k = (obj.k) ;
    k += "haha" ;

}



int main () {
    std::string mystr ("laughter is..") ;
    s mys(mystr) ;
    x(mys) ;

    printf ("%s", mystr.c_str() ) ;
}

The output of this program is laughter is.. and I expect the output to be: laughter is haha

Why doesn't mystr string get haha . I need to store it in a class as a part of my code.

If I had passes mystr by value to function x, the string mystr would have got haha into it.

a) How and when do STL objects get allocated? I supposed mystr is on a stack and must be accessible to all functions called from main() .

b) What if I need to store STL objects in a old fashioned Linked list which needs "void*". Cant I just do:

std::string mystr ("mystring.." );

MyList.Add((void*)&mystr) ;

fun(MyList) ;

Can the function fun, now use and modify mystr by accessing MyList ?

c) As an alternative to (b) , can I use pass by reference. The issue is can I declare a class to keep a reference of mystr? I mean the constructor of MyList can be like this:

class MyList { 
    string& mStr ;
    ...
};


MyList::MyList ( string& mystr ) {
      mStr = mystr ;

}

Is that constructor valid ? Is that class valid?

jww
  • 97,681
  • 90
  • 411
  • 885
  • 5
    I suppose you should read about passing stuff by value and by reference – Marco A. Jul 27 '14 at 10:59
  • Hint: `void x ( s obj )` should be `void x ( s& obj )` – πάντα ῥεῖ Jul 27 '14 at 11:01
  • 2
    possible duplicate of [What's the difference between passing by reference vs. passing by value?](http://stackoverflow.com/questions/373419/whats-the-difference-between-passing-by-reference-vs-passing-by-value) – Marco A. Jul 27 '14 at 11:05
  • 1
    For the future: Please start classes with uppercase letters for easier readability. – Janick Bernet Jul 27 '14 at 11:08
  • You're also overlooking that `string k = (obj.k) ;` makes a copy of `obj.k`. Even if `obj` was passed by reference, modifying `k` still would not modify `obj.k`. To fix, do `string &k = obj.k;` – M.M Jul 27 '14 at 11:08
  • for part `(c)` the syntax is `MyList::MyList(string &mystr): mStr(mystr) {}` – M.M Jul 27 '14 at 11:10
  • Oh yes! That explains, why when I modified program to use string& in my function, still it didnt work. Thanks a lot! @MattMcNabb: why dont we assign values in MyList constructor here? – zombie_process Jul 27 '14 at 11:12
  • @user2773504 If you went `mstr = mystr;` inside a function then it would mean to copy `mystr` to the string that `mstr` refers to. You can only set where a reference is referring to, during initialization of the reference. By the time you enter the constructor body is too late. – M.M Jul 27 '14 at 11:22

3 Answers3

3

Your class is just complicating the situation for you. You have exactly the same problem here:

void x ( string str ) {
    str += "haha" ;
}

int main () {
    std::string mystr ("laughter is..") ;
    x(mystr) ;

    printf ("%s", mystr.c_str() ) ;
}

I've gotten rid of the class. Instead of putting mystr into an s object and passing the s object to x, I just pass mystr directly. x then attempts to add "haha" to the string.

The problem is that x takes its argument by value. If you pass an object by value, you are going to get a copy of it. That is, the str object is a different object to mystr. It's a copy of it, but it's a different object. If you modify str, you're not going to affect mystr at all.

If you wanted x to be able to modify its argument, you'd need to make it take a reference:

void x ( string& str ) {
    str += "haha" ;
}

However, I understand why you introduced the class. You're thinking "Well if I give the string to another object and then pass that object along, the string should be the same both outside and inside the function." That's not the case because your class is storing a copy of the string. That is, your class has a member string k; which will be part of any object of that class type. The string k isn't the same object as mystr.

If you want to modify objects between functions, then you need some form of reference semantics. That means using pointers or references.

As for your questions:

  1. Yes, the string object mystr is on the stack. That has nothing to do with it coming from the standard library though. If you write a declaration inside a function, that object is going to be on the stack, whether it's int x;, string s;, SomeClass c;, or whatever.

    The internal storage of data inside mystr is, on the other hand, dynamically allocated. It has to be because the size of a std::string can vary, but objects in C++ always have fixed size. Some dynamic allocation is necessary. However, you shouldn't need to care about this. This allocation is encapsulated by the class. You can just treat mystr as a string.

  2. Please don't use a linked list that stores void*s. Use std::list instead. If you want a linked list of strings, you want std::list<std::string>. But yes, if you have an object that stores pointers to some other objects and you pass that object around by value, the pointers in the copies will still be pointing at the same locations, so you can still modify the objects that they point to.

  3. If you have a std::list<std::string> and you want to pass it to a function so that the function can modify the contents of the container, then you need to pass it by reference. If you also need the elements of the list to be references to the objects you created outside the list, you need to use a std::list<std::reference_wrapper> instead.

    As far as initialising a reference member is concerned, you need to use a member initialisation list:

    MyList::MyList(string& mystr)
      : mStr(mystr)
    { }
    
Joseph Mansfield
  • 108,238
  • 20
  • 242
  • 324
0

The string k that you manipulate in your function x is a copy of the string k in your object obj. And obj itself is already a copy of what you pass and the string you pass and store in obj is also already a copy. So it's very, very far from the original mystr that you expect to being altered.

To your other questions: a) Yes, objects in this way are stack allocated. Not just stl, any objects. Otherwise you need to use new. b) No you cannot pass it like this, since it's stack allocated the memory will become invalid. You need to heap allocate it using new. c) Yes you can pass by reference, but again, it's important where you allocate things.

As others point out, those are some very basic questions and you need t read about heap vs stack allocation and pass by reference and pass by value first and then have a look at some basic STL classes and containers.

Janick Bernet
  • 20,544
  • 2
  • 29
  • 55
0

Strictly speaking, your question has nothing to do with the STL, even if you accept "STL" as a synonym for the correct "containers, iterators and algorithms of the C++ standard library". std::string was at one point of is history made to appear like a container (a container of characters, that is), but it is generally used in quite a different fashion than "real" container classes like std::vector or std::set.

Anyway,

Why doesnt mystr string get "haha"

Because you don't use references. x modifies a copy of the argument; likewise, string k = (obj.k) creates a copy of the string. Here is the code with references:

void x ( s &obj ) {
    string &k = (obj.k) ;
    k += "haha" ;
}

a) How and when do STL objects get allocated?

The container object itself is allocated as you define it. How it allocates memory internally is defined by its allocator template parameter, by default std::allocator. You don't really want to know the internals of std::allocator - it almost always does the right thing. And I don't think your question is about internal allocations, anyway.

I supposed mystr is on a stack and must be accessible to all functions called from main()

Yes.

b) What if I need to store STL objects in a old fashioned Linked list which needs "void*".

Use std::list<void*>.

But you don't have to do this. Use std::list<std::string> and you likely won't need pointers in your code at all.

As for your further code examples:

std::string mystr ("mystring.." );
MyList.Add((void*)&mystr) ;
fun(MyList) ;

Can the function fun, now use and modify mystr by accessing MyList ?

Yes. However, the code has two problems. The smaller one is (void*)&mystr. Generally, you should avoid C-style casts but use one of static_cast, reinterpret_cast, const_cast or dynamic_cast, depending on which conversion you need. And in this piece of code, you don't need a cast at all, anyway.

The bigger problem is adding the address of a local variable to something which looks like it expects dynamically allocated objects. If you return MyList from a function, mystr will be destroyed and the copied list will contain a pointer to a dead object, eventually leading to undefined results.

In order to solve this, you have to learn more about new, delete and, possibly, smart pointers. This is beyond the scope of a simple answer, and the outcome would probably still be worse than std::list<std::string>.

The issue is can I declare a class to keep a reference of mystr?

Yes, but you should generally avoid it, because it easily leads to dangling references, i.e. references to dead objects, for the reasons explained above.

class MyList { 
    string& mStr ;
    ...
};


MyList::MyList ( string& mystr ) {
      mStr = mystr ;

}

Is that constructor valid ?

No, it won't compile. You'd need to use an initialisation list:

MyList::MyList ( string& mystr ) : myStr(mystr) {}

I can only repeat my recommendation from above. Use std::list<std::string>.

Christian Hackl
  • 27,051
  • 3
  • 32
  • 62