3

I have a class that needs to concatenate two const char* strings during construction, and even use the result (concatenated string) later on in the initialization list.

const char* SUFFIX = "suffix";

class widget {
public:
    widget(const char* prefix) : key(???), cls(key) { };

private:
    const char* key;
    const important_class cls;
}

widget("prefix_"); // key should be prefix_suffix

There is a global (in widget's cpp) const char* suffix that I want to append to user supplied prefix.

How to do it?


BTW. I've heard about string. If I could use string I wouldn't ask here, about const char*

Xlaudius
  • 1,757
  • 2
  • 13
  • 16
  • 7
    You should use a `std::string` (or whatever string class your project uses). That would make this much easier and avoid the problems to get this exception-safe. – JoergB Mar 08 '13 at 12:49
  • 2
    **Why** can you not use `string`? Your class will happily accept `const char *` even if you write it in terms of `string`s: http://liveworkspace.org/code/3eDEfw$4 – us2012 Mar 08 '13 at 12:58
  • This is `c++` not `c-with-classes`. – Peter Wood Mar 08 '13 at 13:13

8 Answers8

3

Using std::string makes your problem a trivial task:

const std::string SUFFIX = "suffix";

class widget {
public:
    widget(std::string const & prefix) 
           : key(prefix + SUFFIX), cls(key)
    { }       // ^^^^^^^^^^^^^^concatenation!

private:
    const std::string key;
    const important_class cls; 
}

widget("prefix_");

If you need const char*, you could still get it by callng key.c_str() which returns const char*. So in your case, it would give you c-string "prefix_suffix".

Also note that the order of declaration is important which you've done correctly : cls must be declared after key as its construction depends on key.

Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • You should at least have a comment to highlight the order dependency for members `key` and `cls`. – JoergB Mar 08 '13 at 12:52
  • @Xlaudius: Do you have any proper reason to stick to `char*`? If not, prefer using `std::string`. – Nawaz Mar 08 '13 at 12:56
  • @Nawaz It is, but that isn't obvious when you see the list of member declarations. A comment would make it it obvious, that (1) you have thought of this and (2) these members must not be reordered when maintaining the code. – JoergB Mar 08 '13 at 13:01
2

Use std::string as an intermediate value:

const char* SUFFIX = "suffix";

class widget {
public:
    widget(const char* prefix) :
    intermediate(string(prefix) + suffix),
    key(intermediate.c_str()),
    cls(key) {
    }

private:
    const std::string intermediate;
    const char* key;
    const important_class cls;
}

widget("prefix_"); // key should be prefix_suffix

The only thing different to your code here is the private member variable, intermediate.

The std::string object, intermediate, manages the dynamic memory needed to hold the concatenation. It will clean up nicely in the event of exceptions, assignment, copying, etc.

As long as the string isn't mutated the const char* returned by c_str() will remain valid. Since intermediate is const, no mutating functions can be called on it, so you shouldn't have problems with cls using the internal buffer.

Peter Wood
  • 23,859
  • 5
  • 60
  • 99
  • and `intermediate` will take care of freeing the memory pointed by `key`? – Xlaudius Mar 08 '13 at 13:18
  • @Xlaudius Yes `intermediate` will clean up properly, and `key` can never point to anything different. `intermediate` will be destroyed after `important_class` and `key`, so it will exist at least as long as it is needed. – Peter Wood Mar 08 '13 at 13:25
  • but isn't it possible that `c_str` will return a copy of the held string, so in your snipped it will leak after destroying an instance of widget? – Xlaudius Mar 08 '13 at 13:27
  • @Xlaudius No, the `const char*` returned by `c_str` is valid as long as `intermediate` is valid. If you pass the pointer somewhere else then yes, after the `widget` is destroyed that pointer will no longer be valid. – Peter Wood Mar 08 '13 at 13:30
  • This is what am not exactly sure about: is it guaranteed that the pointer returned by `c_str()` is *managed* by the original string? Meaning that the pointer is *de facto* a pointer to internal data.. – Xlaudius Mar 08 '13 at 14:05
  • 1
    @Xlaudius There's [an answer to *What is std::string::c_str() lifetime?*](http://stackoverflow.com/a/6456408/1084416). – Peter Wood Mar 08 '13 at 14:11
1

You should use a std::string (or whatever string class your project uses). That would make this much easier and avoid the problems to get this exception-safe.

If you insist,

widget(const char* prefix) : 
    key(strcat(strcpy(new char[strlen(prefix)+strlen(SUFFIX)+1], prefix), SUFFIX)), 
    cls(key) { }

would do it the C way.

The constructor of important_class should not throw exceptions.

And be careful to keep your order of declarations, if you need to use the resulting key to initialize cls.

JoergB
  • 4,383
  • 21
  • 19
0

Better use std::string to put your strings together. You can still call std::string::c_str() to obtain the const char* if needed. std::string::append takes const char* as argument, too. Or use +-operator.

See here.

bash.d
  • 13,029
  • 3
  • 29
  • 42
0

I am not sure you can do that in initialization lists. Surely some other user with more experience can help there... Of course, this may sound obvious but you can use the std::string and just go:

class widget 
{
    public:
    widget(const char* prefix) : key(prefix)
    { 
       string_key.append(suffix);
    };

    private:
    const char* key;
    std::string string_key;
};

Of course, you can just do:

class widget 
{
    public:
    widget(const char* prefix) : key(prefix)
    { 
       key.append(suffix);
    };

    private:
    std::string key;
};

I am missing the whole "important_class" part. What does it represent here?. What are you trying to achieve?.

Hope that helps.

The Marlboro Man
  • 971
  • 7
  • 22
0

I see two options, depending on your needs for value persistency etc.

One, store key as a std::string:

const char* SUFFIX = "suffix";

class widget{
public:
  widget(const char* prefix) : key(std::string(prefix) + suffix), cls(key.c_str())
  {}

private:
  std::string key;
  const important_class cls;
};

Two, use a static initialisation function, something like this:

const char* SUFFIX = "suffix";

class widget{
public:
  widget(const char* prefix) : key(concat_(prefix, suffix)), cls(key)
  {}

private:
  const char* key;
  const important_class cls;

  static const char* concat_(const char* a, const char* b)
  {
    std::string val(a);
    val += b;
    char* ret = new char[val.size() + 1];
    strcpy(ret, val.c_str());
    return val;
  }
};
Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
0

At first sight you question looks like a theoretical quiz, but then I recalled there might be an embedded platform without STL.
Anyway you need to stick to the old-school C-style functions: strlen, strcpy and strcat.
Since you are using classes, I assume the operator new is also supported by your compiler. Before I write the final code here are the steps you need to undertake to concatenate the strings:

const char* key;
key = new char[strlen(prefix) + strlen(SUFFIX) + 1];
strcpy(key, prefix);
strcat(key, SUFFIX);
...
delete [] key;

Luckily both strcpy, and strcat return the destination.
And here is how your code may look:

#include <string.h>

class widget {
public:
  widget(const char* prefix)
    : key(strcat(strcpy(new char[strlen(prefix) + strlen(SUFFIX) + 1], prefix), SUFFIX))
    , cls(key) { };

private:
  const char* key;
  const important_class cls;
};

I didn't debug this code, but it compiles fine, though it looks rather messy... :)
Don't forget to free the buffer in the destructor.
Good luck!

AntonK
  • 1,210
  • 1
  • 16
  • 22
0

If you are not fan of std::string, you can still make your custom function of c-style char* builder, and call the builder inside the constructor.

In the example below, the parent class std::runtime_error(const char*) was successfully initialized from its child class,
and you can catch any kind of error among the child and its parents:

num::dividebyzero_error
std::runtime_error
std::exception

and in any case, err.what() shows the string correctly.

// snprintf(char* result, size_t maxsize, const char* format, args...)
#include <iostream>
class num {
public:
    int value;
    num() : value(0) {}
    num(int newvalue) : value(newvalue) {}
    class dividebyzero_error : public std::runtime_error {
    private:
        const char* makemsg(int dividend) {
            char* msg = (char*)malloc(128);
            snprintf(msg, 128, "You should not divide %d into ZERO.", dividend);
            return msg;
        }
    public:
        dividebyzero_error(int dividend): std::runtime_error(makemsg(dividend)) {} // this works!
    };
    friend num operator/(const num &n1, const num &n2) {
        if (n2.value == 0) throw dividebyzero_error(n1.value);
        return num(n1.value / n2.value);
    }
};
int main() {
    num a(1234);
    num b;
    try {
        num c = a / b;
        std::cout << c.value << '\n';
    } catch (std::exception &err) {
        std::cout << err.what() << '\n';
    }
}
Steven H
  • 1
  • 1