1

I have multiple methods inside my class which need const char * so I convert string to const char * inside the constructor of my class and store it in a local variable with type const char *. The local variable is valid inside the constructor. However, it is empty when I call it on methods of the same class.

If I pass it using const references my problem is fixed but I expected my code works without using const references.

I followed How to convert a std::string to const char* or char*? for other methods to convert the string to const char *.I think the c_str() convert the string correctly.

I want to understand the root cause which cause my local variable to be empty. I prepared example codes for my problem.

Code with problem:

#include <iostream>
#include <string>

using namespace std;

class Config{
string strFileName_ = "/path/filename.ext";
public:
    string getFileName() {return strFileName_;}
};


class Loader{
const char * className_;
public:
    Loader(string name){
        //className_ = name.c_str();   //same result with .data()
        className_ = name.data();
        cout << "[inside Loader constructor]className_ is:" << className_ << endl;
    }

    void loadFile(){
        cout << "[inside loadFile] className_ is:" << className_ << endl;
    }
};

int main(){
    Config cfg;
    Loader ld(cfg.getFileName());
    ld.loadFile();
}

Code with string instead of const char * does not have this problem. As I explained, if I use const references, the problem is not there anymore. Code for references:

#include <iostream>
#include <string>

using namespace std;

class Config{
string strFileName_ = "/path/filename.ext";
public:
    const string &getFileName() {return strFileName_;}
};


class Loader{
const char * className_;
public:
    Loader(const string& name) {
        className_ = name.c_str();
        cout << "[inside Loader constructor]className_ is:" << className_ << endl;
    }

    void loadFile(){
        cout << "[inside loadFile] className_ is:" << className_ << endl;
    }
};

int main(){
    Config cfg;
    Loader ld(cfg.getFileName());
    ld.loadFile();
}
Community
  • 1
  • 1
eSadr
  • 395
  • 5
  • 21
  • Please tell me why you cannot just use `std::string`? What is the benefit to using `const char *` ? – Ed Heal Dec 27 '16 at 02:05
  • Exactly what do you mean by "it is empty"? Is it `NULL` or `nullptr`? What platform/ C++ compiler/run-time are you using? According to [the C++ Standard (C++14)](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4296.pdf), `name.data()` should return an "allocated copy": *points at the first element of an allocated copy of the array whose first element is pointed at by the original value of `str.data()`.* Since it's an allocated copy, I'd think it should be valid no matter what happens to the source `string` object after your new obect's constructor is complete. – Andrew Henle Dec 27 '16 at 02:15
  • I need const char * because I use PETSc library which needs this format. However, my question is not related to the PETSc library and it is general. – eSadr Dec 27 '16 at 02:36
  • I use g++ (GCC) 5.3.1 20160406 (Red Hat 5.3.1-6) with c++11. I checked the value and it is not NULL. – eSadr Dec 27 '16 at 02:39

2 Answers2

4
 Loader(string name){
    //className_ = name.c_str();   //same result with .data()
    className_ = name.data();

The "root cause" is that the pointer that's returned by c_str() or data() points to data that's owned by the std::string. When the std::string gets destroyed, the pointer is no longer valid. It's gone. Its contents ceased to exist. Joined the choir invisible. Gone to meet its maker. It's an ex-pointer.

Here, string name is passed-by-value parameter to the constructor. When the constructor returns, this parameter gets destroyed. The saved pointer to its contents, via either c_str() or data(), is no longer a valid pointer.

The scope and lifetime of objects is a key, fundamental principle of C++. You must fully understand when and how various objects get created or destroyed. C++ doesn't manage this for you, unlike other languages like Java. You have to understand when various objects in a C++ program get destroyed, and for which reason; and what consequences result from that. Here, the consequence is that the saved pointer to the inner contents of the object gets automatically invalidated. Subsequent usage of the pointer produces undefined results.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • That's what I thought, too. But when I looked at the C++ standard (admittedly C++14), it stated that `data()` returns a pointer to an "allocated copy" of the string (see my comment above). Did the behavior change with C++14, or am I misreading something? – Andrew Henle Dec 27 '16 at 02:24
  • Nothing changed. "Allocated" refers to the internal buffer `std::string` has allocated for the string. When the `std::string` gets destroyed, it destroyes its internal allocated buffer. – Sam Varshavchik Dec 27 '16 at 02:34
  • @AndrewHenle: Look at the beginning of the string section under "general requirements" (at `[string.require]`): `References, pointers, and iterators referring to the elements ... may be invalidated by ... calling non-const member functions ...` The destructor is an example of such. –  Dec 27 '16 at 02:37
1

better use this:

 class Config{
        string strFileName_ = "/path/filename.ext";
    public:
        string getFileName() { return strFileName_; }
    };


    class Loader{
        string className_;
    public:
        Loader(string name){
            //className_ = name.c_str();   //same result with .data()
            className_ = name.data();
            cout << "[inside Loader constructor]className_ is:" << className_ << endl;
        }

        void loadFile(){
            cout << "[inside loadFile] className_ is:" << className_ << endl;
        }
    };

    int main(){
        Config cfg;
        Loader ld(cfg.getFileName());
        ld.loadFile();
    }

else if you really want to play with pointers:

 class Config{
        string strFileName_ = "/path/filename.ext";
    public:
        string getFileName() { return strFileName_; }
    };


    class Loader{

        char *className_;

    public:
        Loader(string name){

            className_ = new char[name.size() + 1];
            std::copy(name.begin(), name.end(), className_);
            className_[name.size()] = '\0'; // don't forget the terminating 0

            cout << "[inside Loader constructor]className_ is:" << className_ << endl;
        }

        void loadFile(){
            cout << "[inside loadFile] className_ is:" << className_ << endl;
delete []className_;
        }
    };

    int main(){
        Config cfg;
        Loader ld(cfg.getFileName());
        ld.loadFile();
    }
Muhammad Raza
  • 847
  • 1
  • 8
  • 22