我是C++的新手,我正在努力理解构建类的良好实践.
假设我有一节课Foo:
class Foo {
public:
double foo;
Foo(double foo);
Foo add(Foo f);
}
Run Code Online (Sandbox Code Playgroud)
我想创建一个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);
}
Run Code Online (Sandbox Code Playgroud)
因为我没有为其定义默认构造函数,所以它不起作用
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));
}
Run Code Online (Sandbox Code Playgroud)
注:我必须声明
foo1,并foo2作为const该构造的工作.它仍然失败,因为foo3我正在采取临时结果的地址,这是非法的.
哪个选项更自然(我如何修复错误)?我觉得第一个选项可能更好,但是我的Foo对象必须在内存中创建两次,不是吗?(一次调用构造函数,第二次调用构造函数本身)
任何帮助赞赏.
使用指针作为成员是好的,但在你的情况下,你只是在一个小的打嗝工作,实际上不保证指针的使用,并且使用指针可能是危险的,我将很快指出一个问题.
因为我没有为Foo定义默认构造函数,所以它不起作用.
使用Bar的初始化程序可以轻松解决这个问题:
Bar(const Foo &f1, const Foo &f2) : foo1(f1), foo2(f2), foo3(f1.add(f2)) {}
Run Code Online (Sandbox Code Playgroud)
如下所示:
#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;
}
Run Code Online (Sandbox Code Playgroud)
现场演示:http://ideone.com/iaNzJv
在你的指针解决方案中,你引入了一个明显的指针问题:一个指向临时的指针.
foo3 = &(f1.add(f2));
Run Code Online (Sandbox Code Playgroud)
f1.add返回一个临时Foo,你获取地址,然后它就消失了.这是一个悬垂的指针.
您的指针实现也没有显式地将指针作为其输入,因此f1和f2可能会遇到同样的问题:
Bar(Foo(20), Foo(22)); // two temporary Foos passed by reference
// but having their addresses taken. ouch.
Run Code Online (Sandbox Code Playgroud)
如果你正在指点,最好在你班上的api那样做; 你将不得不关心指向的事物的生命周期,并试图让调用者更容易告诉你这样做.
Bar(Foo* f1, Foo* f2);
Run Code Online (Sandbox Code Playgroud)
但是现在如果你要拥有F3,你将负责管理它的内存:
Bar(Foo* f1, Foo* f2)
: foo1(f1), foo2(f3), foo3(new Foo(*f1.add(*f2)))
{}
~Bar()
{
delete f3;
}
Run Code Online (Sandbox Code Playgroud)
因此,在您的示例中,使用成员可能会更好.
保存对您绝对不想复制的大对象的指针的使用,以及不能使用移动操作的地方.
---编辑---
输送指针的所有权的问题已经很大程度上解决了在现代C++(C++ 11或更高版本),通过"智能指针",尤其是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;
}
Run Code Online (Sandbox Code Playgroud)
现场演示:http://ideone.com/9BtGkn
这样做的优雅之处在于,当Bar超出范围时,unique_ptrs将确保他们拥有的对象被我们破坏 - 我们不必记住delete它们.
在上面的例子中,创建m_f2成员而不是指针可能要好得多.