Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Better way of building dynamic struct with char*?

Tags:

c++

struct

I want to use a C-style API from C++ which takes a variable number of structs with char* members like this:

typedef struct _FOO
{
    const char * name;
    const char * value;
} FOO;

void ApiFunc(FOO const* args, unsigned count);

To fill the parameters, I need to loop over some other data and create FOO entries on the fly. What would be the most elegant way to do that?

The following approach seems simple at first, but does not work (since the string instances go out of scope and are destroyed before the call to ApiFunc()):

// Approach A: this does *not* work
std::vector<FOO> args;

for (...)
{
   string name = ...   // something which gets
   string value = ...  // calculated in the loop
   args.push_back( FOO{name.c_str(), value.c_str()} );
}

ApiFunc( args.data(), args.size() );

Putting the string objects in a vector (to prevent them from being destroyed) doesn't work either - as the strings are copied when put into the vector, and the original ones are still destroyed:

// Approach B: this also does *not* work
std::vector<string> strings;
for (...)
{
   string name = ...   // something which gets
   string value = ...  // calculated in the loop
   strings.push_back( name );
   strings.push_back( value );
   args.push_back( FOO{name.c_str(), value.c_str()} );
}

ApiFunc( args.data(), args.size() );

I can prevent that by creating the string objects on the heap and using auto_ptr to keep track of them, but is there a better way?

// Approach C: this does work
std::vector<auto_ptr<string>> strings;
for (...)
{
   string* name = new ...   
   string* value = new  ... 
   strings.push_back( auto_ptr<string>(name) );
   strings.push_back( value );
   args.push_back( FOO{name.c_str(), value.c_str()} );
}

ApiFunc( args.data(), args.size() );

While approach C. seems to work, I find it rather unobvious / hard to understand. Any suggestions how I could improve it?

like image 272
chrisv Avatar asked Dec 14 '25 05:12

chrisv


2 Answers

I am pretty sure that std::vector<auto_ptr<T>> is not permitted by the standard. Use unique_ptr instead. Alternatively, build the strings in the vector, and then build args from that.

std::vector<std::pair<std::string, std::string>> strings;
for (...)
{
     const std::string name = ...;
     const std::string value = ...;
     strings.push_back( std::make_pair( name, value ) );
}
// Note: This loop must be separate so that 'strings' will not reallocate and potentially
// invalidate the pointers returned by elements in it.
for (const auto& pair : strings)
{
    args.push_back( FOO{pair.first.c_str(), pair.second.c_str()} );
}

ApiFunc( args.data(), args.size() );
like image 157
Martin Bonner supports Monica Avatar answered Dec 15 '25 19:12

Martin Bonner supports Monica


Since you're interfacing with a C API, you're going to have to do things the C API way. That is, allocate heap buffers and deallocate them afterwards. This is actually a prime example of the utility of unique_ptr<T[]>:

//Helper function
std::unique_ptr<char[]> cpp_strdup(const std::string &str)
{
    std::unique_ptr<char[]> ret = new char[str.size() + 1];
    //Must copy the NUL terminator too.
    std::copy(str.data(), str.data() + str.size() + 1, ret.get());
    return ret;
}

//In your function:
std::vector<unique_ptr<char[]>> strings;

for (...)
{
   string name = ...   // something which gets
   strings.push_back(cpp_strdup(name));

   string value = ...  // calculated in the loop
   strings.push_back(cpp_strdup(value));

   args.push_back( FOO{strings[strings.size() - 2].get(), strings[strings.size() - 1].get()} );
}

By using unique_ptr instead of std::string in the strings vector, you neatly avoid reallocating the strings themselves when strings undergoes reallocation.

like image 39
Nicol Bolas Avatar answered Dec 15 '25 17:12

Nicol Bolas