我想询问功能差异;也许会问一个示例场景,我应该从下面主要方法中的一个选项中选择:
#include <iostream>
using namespace std;
class A{
private:
int x, y;
public:
A(int, int);
};
class B{
private:
int *x, *y;
public:
B(int, int);
~B();
};
A:: A(int x, int y){
this->x = x; this->y = y;
}
B:: B(int x, int y){
this->x = new int(x);
this->y = new int(y);
}
B:: ~B(){
delete this->x;
delete this->y;
}
int main(){
int x = 0, y = 0;
A* objA = new A(x, y); // line 1
B objB1(x, y); // line 2
B* objB2 = new B(x, y); // line 3
delete objA;
delete objB2;
return 0;
}
我理解主方法B objB1(x, y)
中的第二个声明明显不同于其他2,但有人可以解释标记为1和3的行中构造函数之间的功能差异吗?两种声明都有不良做法吗?
由于
NAX
更新
首先,我感谢每个人都给出的所有答案,我真的得到了一些很好的见解。我已经编辑了上面的代码作为一些答案指出我没有删除我使用的对象,这是公平的,但这不是我的问题的目的。我只是想了解一下创建类的不同方法之间的功能差异。并且感谢所有针对这一点的人。我正在阅读答案。
答案 0 :(得分:1)
我通常更喜欢A
- 样式对象,除非有令人信服的理由使用B
模式,仅仅因为A
- 样式对象更有效。
例如,当分配A
个对象时,将保留2个int(可能是您机器上的8个字节)的内存,然后通过传递给构造函数的参数进行初始化。分配B
个对象时,将保留2个指针到int
的内存(也可能是您机器上的8个字节),但是当B
个对象时在构造函数中初始化,传递的每个值将被复制到新创建的int
(在堆上),因此总共使用了8个字节的内存。因此,在这个简单的示例中,B
个对象占用的内存是A
个对象的两倍。
此外,每次要访问x
和y
B
对象引用的值时,都需要取消引用指针,这会增加一个间接级别,效率低下(并且,在许多用例中,也可能涉及对安全性进行NULL检查,这会增加一个分支)。当然,每当B
个对象被销毁时,都需要进行额外的堆“清理”。 (如果很多频繁地创建和销毁它们,这会逐渐导致堆碎片。)
答案 1 :(得分:1)
“功能差异......”
在第1行,您可以使用new
关键字在堆上分配类型A的对象。在堆上,为objA
指向的对象分配空间,这意味着在堆上创建2 ints
,连续,符合您的ivar定义。
在第2行,您在堆栈上创建了一个B类的新对象。当它超出范围时,它将自动调用它的析构函数。但是,当分配B时,它将被分配两个 int指针(不是整数)的空间,而这些空间又将在堆上分配,正如您在B中指定的那样构造函数。当objB1
超出范围时,析构函数将成功deleted
指针。
在第3行,您将在堆上创建一个B类的新对象。因此,在堆上为两个 int指针(而不是整数)分配空间,然后通过使用{分配在堆上的其他地方这些整数{1}}关键字。当您new
delete
时,析构函数被调用,因此两个“其他整数”被释放,然后objB2
处的原始对象也从堆中释放。
根据WhozCraig的评论,类objB2
绝对是您在示例中显示的两个首选类定义。
编辑(评论回复):
WhozCraig的链接基本上强烈反对使用原始指针。鉴于这一点,是的,我同意,第2行纯粹是基于内存管理的首选,因为A
在技术上管理自己的内存(尽管它仍然使用原始指针)。
但是,我通常不喜欢(过度)使用B
内部类,因为new
多比等效堆栈(或new
)分配慢。因此,我更喜欢non-new
整个类而不是单个组件,因为它只需要一个new
调用,并且无论如何都会在堆中分配所有的ivars。 (更好的是,new
,但这远远超出了这个问题的范围。)
总结一下:
第2行(类placement new
)在内存管理的基础上是首选,但是比这更好:
B
第1行是最好的,前提是你将它包装在智能指针中,例如A objAOnStack(x, y); // Avoids heap altogether
或std::shared_ptr
或类似的东西。
如果没有智能指针包装器,就不应该考虑第3行(并且通常更好地避免使用嵌套的std::unique_ptr
)。
答案 2 :(得分:1)
您应该为类B定义复制构造函数和赋值运算符。否则,这些指针会出现严重问题。除此之外,第1行和第3行之间没有功能差异。唯一的区别在于实现。
话虽如此,没有理由在B内部使用指针。如果需要固定数量的整数,请使用普通整数或普通数组。如果需要可变数量的整数,请使用std::vector
。如果你真的需要分配动态内存,请非常小心并考虑使用智能指针。
如果你的B类只包含一个[指向]整数的指针,它可能是这样的:
class B
{
private:
int * x;
public:
B (int i) { x = new int(i); }
B (const B & b) { x = new int(*b.x); }
~B() { delete x; }
B & operator= (const B & b) // Corner cases:
{ //
int * p = x; // 1) b and *this might
x = new int(*b.x); // be the same object
delete p; //
return *this; // 2) new might throw
} // an exception
};
即使在评论的角落里,这段代码也会做“The Right Thing(TM)”。
另一种选择是:
#include <utility> // std::swap
class B
{
private:
int * x;
public:
B (int i) { x = new int(i); }
B (const B & b) { x = new int(*b.x); }
~B() { delete x; }
void swap (B & b)
{
using std::swap;
swap (x, b.x);
}
B & operator= (const B & b) // Corner cases:
{ //
B tmp(b); // 1) b and *this might
swap (tmp); // be the same object
return *this; //
} // 2) new might throw
}; // an exception
但是,如果有两个指针 - 就像你的例子中那样 - 你必须两次调用new
。如果第二个new
无法抛出异常,您可能希望自动delete
第一个new
保留的内存...
#include <utility> // std::swap
class B
{
private:
int * x;
int * y;
void init (int i, int j)
{
x = new int(i);
try
{
y = new int(j);
}
catch (...) // first new was OK but
{ // second failed, so undo
delete x; // first allocation and
throw; // continue the exception
}
}
public:
B (int i, int j) { init (i, j); }
B (const B & b) { init (*b.x, *b.y); }
~B() { delete x; delete y; }
void swap (B & b)
{
using std::swap;
swap (x, b.x);
swap (y, b.y);
}
B & operator= (const B & b) // Corner cases:
{ //
B tmp(b); // 1) b and *this might
swap (tmp); // be the same object
return *this; //
} // 2) new might throw
}; // an exception
如果你有三个或四个[指向]整数...代码会变得更加丑陋!这就是智能指针和RAII(资源获取初始化)真正有用的地方:
#include <utility> // std::swap
#include <memory> // std::unique_ptr (or std::auto_ptr)
class B
{
private:
std::auto_ptr<int> x; // If your compiler supports
std::auto_ptr<int> y; // C++11, use unique_ptr instead
public:
B (int i, int j) : x(new int(i)), // If 2nd new
y(new int(j)) {} // fails, 1st is
// undone
B (const B & b) : x(new int(*b.x)),
y(new int(*b.y)) {}
// No destructor is required
void swap (B & b)
{
using std::swap;
swap (x, b.x);
swap (y, b.y);
}
B & operator= (const B & b) // Corner cases:
{ //
B tmp(b); // 1) b and *this might
swap (tmp); // be the same object
return *this; //
} // 2) new might throw
}; // an exception
答案 3 :(得分:1)
一般来说,A类的方式比B类要好得多。除非你有充分的理由,你应该坚持使用类似于A的设计。在简单的情况下,对于像这样的简单数据结构,B类的方式是实施甚至可以被视为不良做法。
这有几个原因,这里没有特别的顺序:
x
和y
)比A实例中的字段慢。当访问B实例的成员时,您所拥有的只是他们的位置指针。因此CPU获取指针,然后它可以知道保存x
和y
值的实际整数的地址,以及它何时可以读取或写入其值。x
和y
存储在连续的内存地址中。这是从CPU缓存中获得最大收益的最佳情况。在B的实例中,实际x
和y
所在的地址可能相距很远,并且您从CPU缓存中获得的利益会减少。delete
一个成员)在B的情况下也是一个问题。请注意,有时,将对象的生命周期与成员数据分离是您真正想要的,但这通常不被认为是好的设计。如果您想了解更多信息,请在C ++中查找 RAII 模式。
顺便说一句,正如其他注释中所指出的那样,您必须为B类实现(或声明private
)复制构造函数和赋值运算符。
由于上述相同的原因,如果可以的话,你应该尽量避免new
你的数据,这意味着在标记为1,2和3的行中,第2行实际上是更好的制作方法实例
答案 4 :(得分:0)
第1行创建objA并留下内存泄漏,因为objA未被删除。如果删除,则成员x和y也将被删除。 objA也支持复制构造函数和赋值运算符。这些电话不会出现问题:
func1(*objA)
A objB = *objA.
如果使用objB2执行相同的行,则会出现内存访问冲突,因为x和y指向的相同内存将被删除两次。您需要创建私有拷贝构造函数和赋值运算符以防止这种情况。
关于方案: