Как я могу предотвратить необходимость копировать строки, переданные в конструктор avr-gcc C ++?

В библиотеке модульного тестирования ArduinoUnit я предоставил механизм для присвоения имени TestSuite. Пользователь библиотеки может написать следующее:

TestSuite suite("my test suite");
// ...
suite.run(); // Suite name is used here

Это ожидаемое использование - имя TestSuite является строковым литералом. Однако, чтобы предотвратить труднообнаруживаемые ошибки, я чувствую себя обязанным учитывать различные варианты использования, например:

char* name = (char*) malloc(14);
strcpy(name, "my test suite");
TestSuite suite(name);
free(name);
// ...
suite.run(); // Suite name is used here

Таким образом, я реализовал TestSuite следующим образом:

class TestSuite {
public:
  TestSuite(const char* name) {
    name_ = (char*) malloc(strlen(name) + 1);
    strcpy(name_, name);
  }

  ~TestSuite() {
    free(name_);
  }

private:
  char* name_;
};

Не говоря уже о том, что не удается справиться с ошибками выделения памяти в конструкторе, я бы предпочел просто выделить указатель на переменную-член следующим образом:

class TestSuite {
public:
  TestSuite(const char* name) : name_(name) {
  }

private:
  const char* name_;
};

Есть ли способ изменить интерфейс, чтобы заставить его использовать «правильно», чтобы избавиться от динамического распределения памяти?


person Matthew Murdoch    schedule 22.06.2009    source источник


Ответы (6)


Что, если вы предоставите два перегруженных конструктора?

TestSuite(const char* name) ...
TestSuite(char* name) ...

Если вызывается с const char*, тогда конструктор может сделать копию указателя, предполагая, что строка не исчезнет. При вызове с char* конструктор может сделать копию всей строки.

Обратите внимание, что этот механизм все еще можно нарушить, передав const char* конструктору, когда name фактически выделяется динамически. Однако этого может быть достаточно для ваших целей.

Должен отметить, что я никогда не видел, чтобы эта техника использовалась в API, это была просто мысль, которая пришла мне в голову, когда я читал ваш вопрос.

person Greg Hewgill    schedule 22.06.2009
comment
+1 - Отличная идея. Вы правы, что его можно подделать, но он должен обеспечивать лучшую защиту от неправильного использования, чем оригинал. Очевидно, что если кто-то захочет сломать его, он сможет, но это верно для любого C / C ++. Я попробую посмотреть, как это работает на практике. Спасибо. - person Matthew Murdoch; 23.06.2009

Документация. Например,

/**
* Test suite constructor.
* @param name test suite name cstring, shared
*/
TestSuite(char const *name) {
// ...

Общий указатель подразумевает, что указанный объект должен быть живым в течение всего времени существования этого объекта.

person laalto    schedule 22.06.2009
comment
+1 Очевидно, я думал об этом, но предполагаю, что, как и я, большинство людей не используют RTFM! Я надеюсь на дизайнерское решение кода. - person Matthew Murdoch; 22.06.2009
comment
Извините, но для меня разделение ничего не значит. Может быть, если вы настаиваете на использовании документации, лучше писать понятный текст. - person Mot; 27.06.2011

Что ж, вы можете использовать std :: string, который позаботится обо всем распределении памяти

class TestSuite {
public:
  TestSuite(const std::string &name):name_(name) {
  }

  ~TestSuite() {
  }

private:
  std::string name_;
};

Изменить: если вы хотите избежать вызова malloc (), вы можете сделать следующее:

class TestSuite {
public:
  TestSuite(const char *name){
    memcpy(name_, name, min(16, strlen(name));
  }

private:
  char name_[16];
};

Однако это приведет к потере некоторой памяти, что может быть проблемой на встроенных платформах.

person Ben    schedule 22.06.2009
comment
Я бы хотел, но avrlibc, используемый Arduino (и avr-gcc), не имеет реализации STL ... - person Matthew Murdoch; 22.06.2009
comment
Ох, хорошо ! Возможно, тогда вам стоит реализовать что-то вроде строки stl, чтобы вам не приходилось иметь дело с malloc. В этом суть ООП. - person Ben; 22.06.2009
comment
Я мог бы это сделать, но под ним все равно будет вызываться malloc (), не так ли, поэтому я не уверен, что действительно что-то получил (кроме еще некоторого кода, который нужно поддерживать!). - person Matthew Murdoch; 22.06.2009
comment
Правильно ! но я не понимаю твоего вопроса. Вы хотите быть уверены, что имя является указателем на действительный char *? Собственно, что вы имеете в виду под «правильным использованием»? - person Ben; 22.06.2009

Имейте char name[XYZ] член вашего TestSuite (с XYZ, достаточно большим для удобного отображения имени) и используйте strncpy для копирования строки (с максимальной длиной XYZ-1).

person rlerallut    schedule 22.06.2009
comment
+1 но я бы не хотел выделять дополнительную память, если это возможно. Я хочу заставить клиентский код вызывать код библиотеки со строковым литералом. - person Matthew Murdoch; 22.06.2009

Почему вы используете char * и malloc, когда у вас есть хороший строковый класс C ++, который может принимать строковый литерал или char * в своем конструкторе?

person Ksempac    schedule 22.06.2009
comment
Потому что, к сожалению, avrlibc, используемый Arduino (и avr-gcc), не имеет реализации STL ... - person Matthew Murdoch; 22.06.2009

Умеете ли вы использовать std :: string? Вы можете использовать его как std::string name_, а STL позаботится о распределении памяти за вас.

class TestSuite {
    public:
      TestSuite(const char* name) : name_(name) {}

      ~TestSuite() {}

    private:
      std::string name_;
};

Не забудьте включить <string>.

Справка

person DeadHead    schedule 22.06.2009
comment
Никакой STL не поддерживается на Arduino (avr-gcc / avrlibc). - person Matthew Murdoch; 22.06.2009