我对C ++很新,我试图理解构建类的良好实践。
我们说我有一个班级Foo
:
class Foo {
public:
double foo;
Foo(double foo);
Foo add(Foo f);
}
我想创建一个类Bar
,它由两个Foo
个对象构成,并在构造时创建第三个。
第一个选项:作为类成员的对象
class Bar {
public:
Foo foo1;
Foo foo2;
Foo foo3;
Bar(const Foo &f1, const Foo &f2);
}
Bar::Bar(const Foo &f1, const Foo &f2):
{
foo1 = f1;
foo2 = f2;
foo3 = f1.add(f2);
}
因为我没有为
Foo
定义默认构造函数,所以它不起作用。
第二个选项:指针作为班级成员
class Bar {
public:
const Foo* foo1;
const Foo* foo2;
const Foo* foo3;
Bar(const Foo &f1, const Foo &f2);
}
Bar::Bar(const Foo &f1, const Foo &f2):
{
foo1 = &f1;
foo2 = &f2;
foo3 = &(f1.add(f2));
}
注意:我必须将
foo1
和foo2
声明为const
才能使构造函数正常工作。 它仍然失败,因为foo3
我正在取一个临时结果的地址,这是非法的。
哪个选项更自然(我如何修复错误)?我觉得第一个选项可能更好,但是我的Foo
对象必须在内存中创建两次,不是吗? (一次调用构造函数,第二次调用构造函数本身)
任何帮助表示感谢。
答案 0 :(得分:6)
使用指针作为成员很好,但在你的情况下,你只是在处理一个小小的打嗝,它确实不能保证使用指针,使用指针可能是危险的,如问题我很快就会指出。
原样,它不起作用,因为我没有为Foo定义默认构造函数。
使用Bar的初始值设定项可以轻松解决这个问题:
Bar(const Foo &f1, const Foo &f2) : foo1(f1), foo2(f2), foo3(f1.add(f2)) {}
如下所示:
#include <iostream>
class Foo {
public:
double m_foo;
Foo(double foo) : m_foo(foo) {}
Foo add(Foo f) { f.m_foo += m_foo; return f; } // returns temporary!
};
class Bar {
public:
Foo m_foo1;
Foo m_foo2;
Foo m_foo3;
Bar(const Foo &foo1, const Foo &foo2);
};
Bar::Bar(const Foo &foo1, const Foo &foo2)
: m_foo1(foo1)
, m_foo2(foo2)
, m_foo3(m_foo1.add(m_foo2))
{
}
int main() {
Foo foo1(20.0);
Foo foo2(22.0);
Bar bar(foo1, foo2);
std::cout << bar.m_foo3.m_foo << "\n";
return 0;
}
在你的指针解决方案中,你引入了一个明显的指针问题:一个指向临时的指针。
foo3 = &(f1.add(f2));
f1.add返回一个临时Foo,你获取地址,然后它就消失了。这是一个悬垂的指针。
您的指针实现也没有明确地将指针作为其输入,因此f1和f2可能会遇到同样的问题:
Bar(Foo(20), Foo(22)); // two temporary Foos passed by reference
// but having their addresses taken. ouch.
如果您正在指点,最好在您的课堂上做到这一点;你将不得不关心指向的事物的生命周期,并试图让调用者更容易告诉你这样做。
Bar(Foo* f1, Foo* f2);
但是现在如果你要拥有F3,你将负责管理它的记忆:
Bar(Foo* f1, Foo* f2)
: foo1(f1), foo2(f3), foo3(new Foo(*f1.add(*f2)))
{}
~Bar()
{
delete f3;
}
因此,在您的示例中,使用成员可能会更好。
将指针用于您绝对不想复制的大型对象,以及不能使用移动操作的地方。
---编辑---
传递指针所有权的问题已经在现代C ++(C ++ 11及更高版本),智能指针&#34;,特别是std::unique_ptr
和std::shared_ptr
中得到了很大的解决。
通常认为使用这些而不是原始指针是最佳实践,尽管它需要学习一些较新的C ++概念。
#include <memory>
struct Foo {};
class Bar {
public:
std::unique_ptr<Foo> m_f1; // we will own this
std::unique_ptr<Foo> m_f2; // and this
Bar(std::unique_ptr<Foo> f1) // caller must pass ownership
: m_f1(std::move(f1)) // assume ownership
, m_f2(std::make_unique<Foo>()) // create a new object
{}
~Bar()
{
// nothing to do here
}
};
int main() {
auto f = std::make_unique<Foo>();
Bar(std::move(f)); // the 'move' emphasizes that
// we're giving you ownership
// 'f' is now invalid.
return 0;
}
优雅的是,当Bar超出范围时,unique_ptr
将确保他们拥有的对象被我们破坏 - 我们不必记住{{1}他们。
在上面的例子中,将delete
作为成员而不是指针可能要好得多。
答案 1 :(得分:4)
如果对象不太昂贵而无法传递,我建议使用对象作为成员。
如果由于某种原因需要使用指针,则需要制定所有权政策。 Bar
对象是否拥有对象? Bar
只保存指向对象的指针,但不负责释放它们使用的资源吗?
如果Bar
拥有Foo
个对象,则更喜欢使用其中一个智能指针。您需要使用new
制作这些对象的副本并保留这些指针。
以下是我的看法:
class Bar {
public:
std::unique_ptr<Foo> foo1;
std::unique_ptr<Foo> foo2;
std::unique_ptr<Foo> foo3;
Bar(const Foo &f1, const Foo &f2) : foo1(new Foo(f1)), ... {}
};
std::unique_ptr
没有复制构造函数。因此,您必须为Bar
提供复制构造函数,并从副本中正确初始化其成员。
如果Bar
不拥有Foo
个对象,您可以通过使用引用作为成员数据来获取。
class Bar {
public:
Foo const& foo1;
Foo const& foo2;
Foo const& foo3;
Bar(const Foo &f1, const Foo &f2) : foo1(f1), ... {}
};
答案 2 :(得分:0)
我认为对象与原始变量相同是无稽之谈。
class Foo {
public:
double _stocks;
Business* _business;
Foo(double stocks, Business* business):_stocks(stocks), _business(business){}
Foo* add(const Foo& f) {
_stocks += f._stocks;
_busines->merge(f._business);
return this;
}
virtual ~Foo() { delete _business; }
}
class Bar {
public:
Foo* _foo1;
Foo* _foosub;
// Foo* _foo3;
Bar(Foo* f1, Foo* f2); // unable const for f1 at least
}
Bar::Bar(Foo* f1, Foo* f2):
{
_foo1 = f1;
_foosub = f2;
_foo1.add(*f2);
// _foo3 is the same as _foo1
}
void main() {
Foo company1(100.00, BusinessFactory.create("car"));
Foo company2(2000.00, BusinessFactory.create("food"));
Bar conglomerate(&company1, &company2);
// to be continued
}