tags:

views:

482

answers:

8
+2  Q: 

Copy const char*

Hello,

I'm receiving a c-string as a parameter from a function, but the argument I receive is going to be destroyed later. So I want to make a copy of it.

Here's what I mean:

class MyClass
{
private:
 const char *filename;

public:
 void func (const char *_filename);
}

void MyClass::func (const char *_filename)
{
 filename = _filename; //This isn't going to work
}

What I want to achieve is not simply assign one memory address to another but to copy contents. I want to have filename as "const char*" and not as "char*".

I tried to use strcpy but it requires the destination string to be non-const.

Is there a way around? Something without using const_cast on filename?

Thanks.

+17  A: 

Use a std::string to copy the value, since you are already using C++. If you need a const char* from that, use c_str().

class MyClass
{
private:
    std::string filename;
public:
    void setFilename(const char *source)
    {
        filename = std::string(source);
    }

    const char *getRawFileName() const
    {
        return filename.c_str();
    }
}
Pontus Gagge
Even better, use implicit conversion: filename = source;
Tronic
Implicit conversions are NEVER "better".
Peter Alexander
It's actually not conversion, as string has op= overloaded for char const*, but it's still roughly 13 times better.
Roger Pate
Hmmm... `filename.assign(source)`?
D.Shawley
+2  A: 

You have to decide whether you want your file name to be const (so it cannot be changed) or non-const (so it can be changed in MyClass::func).

Patrick
+1  A: 

[Assuming you continue implementing your class' internals in the C-style, which may or may not be beneficial in terms of development and execution speed (depending on the whole project's design) but is generally not recommended in favor of std::string and friends.]

Turning

const char *filename;

into

char *filename;

will not make you happy with the strcpy, since you actually need some memory for a copy of your string :)

For the manual memory management code part, please see Tadeusz Kopec's answer, which seems to have it all right.

Also, keep in mind that there is a difference between

const char *filename; // "filename" points to "const char" 
                      //  and is not const itself
char const *filename; // semantically the same as above

and

char * const filename; // "filename" is const and points to "char", 
                       //  which is not const

In the first case, you can make filename point to any other const char string, in the second, you can only change that string "in-place" (so keeping the filename value the same, as it points to the same memory location). Of course one can combine these two (or none of them) if needed.

P.S. If you name your member function's parameter _filename only to avoid naming collision with the member variable filename, you can just prefix it with this (and get rid of the underscore):

void MyClass::func (const char *filename)
{
 ...
 this.filename = copy;
}
mlvljr
...without allocating memory first? Ouch!
Pontus Gagge
ah ,really :)) [stubstub]
mlvljr
OK, that's workable. Always nice to make the case for C++ by showing the C way of doing things! ;-)
Pontus Gagge
mlvljr
Gahhh... no mention of freeing the memory in the destructor?
D.Shawley
@ D.Shawley This is answer clearly downvote-driven, fixing:))
mlvljr
Ouch! Gahh! ..where are the copy construction and assignment mentioned? Downvoting myself. ..Failed :(
mlvljr
@mlvljr Hehehe ... I forgot about those ;)
D.Shawley
mlvljr
A: 

char const* implies that the class does not own the memory associated with it. The owner always needs a non-const pointer because otherwise the memory couldn't be freed. When you have non-const pointer, you can allocate the memory for it and then use strcpy (or memcpy) to copy the string itself. However, in your situation using std::string instead is a much better option.

Tronic
No, the pointer isn’t const here, the *memory* is. Having a pointer to `char const` is perfectly fine and you can absolute free its memory.
Konrad Rudolph
Even if the pointer was const (such as `char* const`), you could delete[] or free() it.
Roger Pate
The term const pointer usually refers to "pointer to const" because const-valued pointers are so useless and thus seldom used. I prefer to use that term even though it is somewhat ambiguous because the alternatives (e.g. pointer to const) are cumbersome.
Tronic
@Konrad Rudolph: free() requires pointer to non-const, so you cannot actually free the memory without casting away the const. delete[] appears to accept const pointers too, but generally it is considered a bad practice to do so because it would violate the no modifications principle associated with const.
Tronic
@Tronic: Even if it was "pointer to const" (such as `char const*`), you could delete[] or free() it. (free() requires a const_cast, but this is purely due to C's legacy, as shown by how delete/delete[] don't have that minor issue.) I think the real problem is you're trying to imply ownership by const, which isn't something you can do. Compare string::c_str and taking the address of vector::front (e.g. ` there's differing const and yet both pieces of memory are owned by string and vector, respectively.
Roger Pate
You have Linus Torvalds to back up your point of view, but most other programmers seem to disagree. As for ownership, it only works one way, not the other (non-const does not imply anything about ownership).
Tronic
@Tronic: What? free() dates back to a time *before C had the const keyword*, and that's the legacy I mean. I think you're confused about const-correctness. Destroying const objects is fine and required. Notice both ints here are destroyed: `int main() { int const* p = new int const(42); delete p; int const other = 42; }`
Roger Pate
I am talking about good programming practices, not what is possible in the language. If I pass some function a char const*, I most certainly assume that my characters are still in their original state and usable when the call returns. A program that delete[]s the memory pointed to by such a pointer is extremely likely to cause confusion among other programmers.
Tronic
@Tronic: That has nothing to do with const and everything to do with ownership assumed not to be transferred implicitly. That's the whole reason explicit transfers of ownership, such as auto_ptr, unique_ptr, shared_ptr, etc. are recommended. That's one reason why realloc is problematic (it requires implicit shared-transfer on passing and then transfers back when returning).
Roger Pate
Since there clearly is dispute over what I previously considered a consensus among most programmers, I posted it as a question here: http://stackoverflow.com/questions/2288291/is-it-a-good-practice-to-free-the-memory-via-a-pointer-to-const
Tronic
Yet another downvoter who apparently seriously thinks that this answer is worth downvoting but not worth a comment. Sigh.
Tronic
@Tronic: Been there and it can be frustrating, but it's only a few points of rep and nothing to worry about in the big picture. You were right to use a question instead of continuing the comments, but I just wish you could've done it less argumentatively (no need to mention Linus; request authoritative reference and then characterize "many" "programmers" and "most libraries"; etc.).
Roger Pate
A: 

If you want to stick to plain C, use strncpy. But I agree with Ilya, use std::string as it's already C++. If it's your application that's calling your method, you could even receive a std::string in the first place as the original argument is going to be destroyed.

Andy Shellam
A: 

Why do you have it as const, If you need to change them in one of the methods of the class.

Anyways, non-static const data members and reference data members cannot be assigned values; you should use initialization list with the constructor to initialize them.

MyClass::MyClass(const char *_filename) : filename( _filename ) 
{ 
   // filename = _filename; This isn't going to work 
}

An initializer can also call a function as below

MyClass::MyClass(const char *_filename) : filename( getfilename() ) 
{ 
   // filename = _filename; This isn't going to work 
}

Didn't verify this particular case which is the apt one, but initialization list is the way to assign values to non static const data members.

Narendra N
+2  A: 

I agree that the best thing (at least without knowing anything more about your problem) is to use std::string. But if you insist on managing memory by yourself, you have to manage it completely. So the C++ way:

class MyClass
{
private:
 const char *filename;

 MyClass(const MyClass&); // no implementation
 MyClass operator=(const MyClass &); // no implementation

public:
 MyClass() {filename = 0;}
 ~MyClass() {delete[] filename;}

 void func (const char *_filename);
}

void MyClass::func (const char *_filename)
{
 const size_t len = strlen(_filename);
 char * tmp_filename = new char[len + 1];
 strncpy(tmp_filename, _filename, len);
 tmp_filename[len] = '\0'; // I'm paranoid, maybe someone has changed something in _filename :-)
 delete[] filename;
 filename = tmp_filename;
}

and the C way

class MyClass
{
private:
 const char *filename;

 MyClass(const MyClass&); // no implementation
 MyClass operator=(const MyClass &); // no implementation

public:
 MyClass() {filename = 0;}
 ~MyClass() {free(filename);}

 void func (const char *_filename);
}

void MyClass::func (const char *_filename)
{
 free(filename);
 filename = strdup(_filename); // easier than C++, isn't it?
}
Tadeusz Kopec
Your class also needs a copy constructor and assignment operator.
anon
Thanks. Fixed it by making MyClass uncopyable :-)
Tadeusz Kopec
`//I'm paranoid, maybe someone has changed something in _filename :-)` An evil thread? :))
mlvljr
+1  A: 

There's a function in the Standard C library (if you want to go the C route) called _strdup. It uses malloc to do the actual allocation so you will need to call free when you're done with the string.

So for example,

void MyClass::func (const char *_filename)
{
    if (filename)
    {
        free(filename);
    }
    filename = _strdup(_filename);
}

Of course, don't forget to free the filename in your destructor.

Cthutu
strdup isn't standard
anon
@Neil Butterworth you again! :))
mlvljr
"strdup" is POSIX and is being deprecated. However "_strdup" is ISO C++ conformant.
Cthutu