0

I'm currently creating an app to launch external apps. The signature to launch the external apps is:

int launchApp(int argc, char** argv); // argc = amount of arguments, argv = arguments

To add arguments to a std::vector<char *> structure I use the following lambda:

auto addArgument = [](std::vector<char *> & lArguments,
                       const std::string & sArgument)
{
   auto cstr = new char[sArgument.size() + 1];
   std::copy(sArgument.cbegin(), sArgument.cend(), cstr);
   cstr[sArgument.size()] = '\0';
   lArguments.push_back(cstr);
};

And launching an external app:

std::vector<char *> lArguments;
addArgument(lArguments, "Argument 1");
addArgument(lArguments, "Argument 2");
launchApp(lArguments.size(),static_cast<char**>(lArguments.data());
//... Clean up arguments 

How would I do this in a RAII manner instead? I was thinking of using a std::vector<std::vector<char>> instead. However, how can I then pass the underlying raw data (char**) to launchApp()? static_cast<char**>(lArguments.data()) wouldn't work...

Andrew
  • 5,212
  • 1
  • 22
  • 40
A.Fagrell
  • 1,052
  • 8
  • 21
  • I wonder if `std::vector>` can work... `std::unique_ptr` can have no overhead beyond the raw pointer it holds, but I don't know if it's safe to assume you can simply `reinterpret_cast` the vectors `data` pointer. – StoryTeller - Unslander Monica May 11 '16 at 11:38

2 Answers2

1

I usually do this in two parts:

  1. Build a std::vector<std::string> containing the actual arguments.
  2. Build a std::vector<const char*> where each element is the .data() of the corresponding element in the vector of strings.

The first vector, and the strings contained within it, handle your allocation, resizing, etc. while the second simply acts as an index into the memory that is being managed by the first. The lifetime of your second vector should be shorter than that of the first, which you can guarantee by wrapping both in a class.

Example:

#include <string>
#include <vector>
#include <unistd.h>

int main() {
    std::vector<std::string> args = {"echo", "hello", "world"};
    std::vector<const char*> argv;
    argv.reserve(args.size());
    for (auto& arg : args) {
        argv.push_back(arg.data());
    }

    execvp("echo", const_cast<char**>(argv.data()));    
}

(Note the ugly const_cast. Depending on how you look at it, this is either because the exec* family of functions don't follow const correctness, or because std::string::data() does not have a non-const version (prior to C++17)).


Or, with the class to wrap it all up nicely:

#include <string>
#include <vector>
#include <unistd.h>

class ExecArguments {
 public:
  ExecArguments(std::initializer_list<std::string> arguments)
   : args(arguments) {
       for (auto& arg : args) {
           argv.push_back(const_cast<char*>(arg.data()));
       }
   }

   char** data() {
       return const_cast<char**>(argv.data());
   }

 private:
  std::vector<std::string> args;
  std::vector<char*> argv;
};

int main() {
    ExecArguments args{"echo", "hello", "world"};

    execvp(args.data()[0], args.data());    
}
Andrew
  • 5,212
  • 1
  • 22
  • 40
  • Thanks, I like this answer, however, it's a bit unfortunate you have to create another vector with extra overhead just to pass it as the argument. – A.Fagrell May 11 '16 at 12:48
  • @A.Fagrell Yeah, it is a little. Although the second vector has pretty tiny overhead, particularly if you create it with the appropriate size to begin with. – Andrew May 11 '16 at 12:50
-1
  • Collect your parameters as regular strings.
  • Use an inner class that:
    • provides a transparent implicit view to a raw array of pointers, and
    • takes care of managing this pointer array at the same time.

An instance of this inner class is returned by some access method. Its lifetime spans the invoking statement. See below for an example:

   #include <iostream>
   #include <vector>

   class CollectArgs : public std::vector<std::string> {
     // just for providing the raw view and taking care of its memory
     class RawArgs {
       std::vector<char const*>  m_argv;
     public:
       RawArgs(std::vector<char const*>  argv) {
         std::swap(argv, m_argv);
       }
       ~RawArgs() {}

     public:
       operator char const* const*() const { return  m_argv.data(); }
     };

   public:
     RawArgs raw_args() const {
       std::vector<char const*>  argv;
       for(std::string const &arg : *this)  argv.push_back(arg.c_str());
       return  argv;
     }
   };

   // the application launcher
   void call(unsigned  argc, char const *const *argv) {
     for(unsigned  i = 0; i < argc; i++) {
       std::cout << argv[i] << std::endl;
     }
   }

   int main() {
     CollectArgs  args;
     args.push_back("Arg1");
     args.push_back("Arg2");

     // create the raw view and have it destroyed immediately after this statement
     call(args.size(), args.raw_args());
   }
Thomas B Preusser
  • 1,159
  • 5
  • 10