Malloc на структуре, содержащей std :: vector - PullRequest
2 голосов
/ 17 августа 2011

Вот ситуация:

Я использую malloc для выделения памяти для структуры.Структура содержит различные элементы, такие как указатели, строковые переменные и векторы.

Дело в том, что когда мы используем malloc, конструкторы не вызываются.Используя код, подобный следующему, я столкнулся с ситуацией, когда некоторые переменные работали, а другие - нет.

Примечание. Следующий код не компилируется.Его цель - только проиллюстрировать ситуацию.

struct MyStruct 
{
    MyClass*    mFirstClass;
    bool        mBool;
    std::string mString;
    std::vector<MyClass> mVector;
};


int main()  
{  

    MyStruct* wMyStructure;  
    wMyStructure = (MyStruct*) malloc (sizeof(MyStruct));  

    MyClass wMyClassObject;

    wMyStructure->mFirstClass = new MyClass();  
    wMyStructure->mFirstClass->func();  
    wMyStructure->mBool = false;  
    wMyStructure->mString = "aString";  
    wMyStructure->mVector.push_back(wMyClassObject);

    return 0;
}

При использовании указателей вместо этих переменных (std::string* mString) с последующим вызовом конструктора объекта (mString = new std::string;) Исключения не генерируются.

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

Это оставляло меня со многимивопросы:

  1. Когда объект выдаст исключение, если конструктор не использовался?

  2. В ситуации, с которой я столкнулся, проблема была вызвана только вектором.Можно ли оставить mString как есть или я должен назвать его конструктором?

  3. Какой самый безопасный способ, используя malloc, сделать все это?

Ответы [ 5 ]

5 голосов
/ 17 августа 2011

Использование объекта без его конструирования должно быть неопределенным поведением. Все может случиться в любой момент. Если вы сделаете это, вы не должны полагаться на то, что какая-то часть вашего кода будет работать гладко, потому что в этом случае язык ничего не гарантирует.

2 голосов
/ 17 августа 2011

Ваш код вызывает неопределенное поведение, поскольку ваш wMyStructure не указывает на объект, поэтому вы не можете использовать оператор доступа -> для него.

Объект только начинает свою жизнь после того, как его конструктор завершил .Поскольку вы не вызываете конструктор, у вас нет объекта.

(Если бы ваша структура была POD, то есть состояла только из примитивных типов и POD, то это было бы нормально, потому что POD имеют тривиальные конструкторы, которые ничего не делают.)

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

Если вы хотите отделить управление памятью от конструирования объекта, вы можете использовать синтаксис размещения:

// get some memory
char arena[HUGE_VAL];
void * morespace = malloc(HUGE_VAL);

// construct some objects
MyClass   * px = new (arena + 2000) MyClass;  // default constructor
YourClass * py = new (morespace + 5000) YourClass(1, -.5, 'x');  // non-default constructor

(Вы должны уничтожить эти объекты вручную, px->~MyClass(); и т. д.., когда вы закончите с ними.)

1 голос
/ 17 августа 2011

1) Когда объект сгенерирует исключение, если конструктор не использовался?

Если вы не вызываете конструктор, означает, что нет объекта.Вы только что выделили немного места.

2) В ситуации, которую я испытал, проблема была вызвана только вектором.Можно ли оставить mString как есть или я должен назвать ее конструктором?

Это все неопределенное поведение, примерно все, что может произойти.Нет никаких правил.

3) Какой самый безопасный способ, используя malloc, сделать все это?

Самый безопасный способ будет не для использования malloc, но для выделения используйте new, который будет вызывать конструкторы.Это так просто, как этот

MyStruct* wMyStructure = new MyStruct;
1 голос
/ 17 августа 2011

Неопределенное поведение - использовать неинициализированный объект.Исключение может быть выдано в любое время - или не может вообще.

0 голосов
/ 29 июня 2015

Ни один из других ответов не объясняет, что делает компилятор. Я постараюсь объяснить.

Когда вы вызываете malloc, программа резервирует некоторое пространство памяти для структуры. Это пространство заполнено мусором памяти (то есть случайными числами вместо полей структуры).

Теперь рассмотрим этот код:

// (tested on g++ 5.1.0 on linux)
#include <iostream>
#include <stdlib.h>

struct S {
  int a;
};

int main() {
  S* s = (S*)malloc(sizeof(S));
  s->a = 10;

  *((int*)s) = 20;

  std::cout << s->a << std::endl; // 20 
}

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

Но в C ++ вы можете перегружать операторы. Теперь представьте, что произойдет, если перегруженному оператору присваивания потребуется инициализация класса, как показано в коде ниже:

// (tested on g++ 5.1.0 on linux)
#include <iostream>
#include <stdlib.h>

class C {
  public:
  int answer;
  int n;
  C(int n) { this->n = n; this->answer = 42; }
  C& operator=(const C& rhs) {
    if(answer != 42) throw "ERROR";
    this->n = rhs.n; return *this;
  }
};

struct S {
  int a;
  C c;
};

int main() {
  S* s = (S*)malloc(sizeof(S));

  C c(10);
  C c2(20);

  c = c2; // OK

  std::cout << c.n << std::endl; // 20

  s->c = c; // Not OK
            // Throw "ERROR"

  std::cout << s->c.n << std::endl; // 20
}

Когда выполняется s->c = c, оператор присваивания проверяет, является ли s->c.answer значением 42, в противном случае выдает ошибку.

Таким образом, вы можете сделать только то, что сделали в своем примере, если знаете, что перегруженный оператор присваивания класса std::vector не ожидает инициализированный вектор. Я никогда не читал исходный код этого класса, но держу пари, что он ожидает.

Так что это не рекомендуется делать, но это не невозможно сделать с безопасностью, если вам действительно нужно. Вам просто нужно убедиться, что вы знаете поведение всех используемых вами операторов присваивания.

В вашем примере, если вам действительно нужна std::vector в структуре, вы можете использовать векторный указатель:

class MyClass { ... };

struct S {
  std::vector<MyClass>* vec;
}

int main() {
  S s;
  MyClass c;
  s.vec = new std::vector<MyClass>();
  s.vec->push_back(c);
}
...