情况如下:
我使用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没有问题,但是当它来到向量时,应用程序会自动退出。
这给我留下了很多问题:
如果没有使用构造函数,对象何时会抛出异常?
在我遇到的情况下,只有向量引起了问题。 mString可以保持原样,还是应该调用它的构造函数?
使用malloc做最重要的事情最安全的方法是什么?
答案 0 :(得分:5)
使用对象而不构造它必须是未定义的行为。任何事情都可能随时发生。如果你这样做,你不能依赖代码的任何部分来顺利运行,因为在这种情况下语言不保证任何东西。
答案 1 :(得分:2)
您的代码会导致未定义的行为,因为您的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();
等。)
答案 2 :(得分:1)
使用非初始化对象是未定义的行为。可能在任何时候抛出异常 - 或者根本不抛出。
答案 3 :(得分:1)
1)如果没有使用构造函数,对象何时会抛出异常?
如果你不调用构造函数,那么 就没有对象。你刚刚分配了一些空间。
2)在我遇到的情况下,只有矢量引起了问题。 mString可以原样保留,还是应该调用它的构造函数?
这是所有未定义的行为,只是任何可能发生。没有规则。
3)使用malloc做最重要的事情最安全的方法是什么?
最安全的方法是不使用malloc,但使用将调用构造函数的new
进行分配。它就像这个一样简单
MyStruct* wMyStructure = new MyStruct;
答案 4 :(得分:0)
其他答案似乎都没有解释编译器正在做什么。我会试着解释一下。
当你调用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);
}