从同一 class 的另一个成员中引用 class 成员对象是不好的做法吗?
Is it bad practice to reference class member object from within another member of the same class?
我是 C++ 的新手(具有 Java 背景),我正在尝试了解 class 设计的原则和实践。
根据我已经阅读的内容,在我看来,人们应该更喜欢将 class 成员作为对象而不是将它们作为指针(或引用)。 - 如果这有什么不对,请纠正我。
基于此,这是我的设计(版本 1):
class Outer {
Some_type _member;
Inner _inner;
};
class Inner {
Some_type* _ptr_to_outer_member;
};
我知道需要保证引用对象 (Outer._member)
的生命周期超过引用对象 (Inner)
及其指针 _ptr_to_outer_member
的生命周期,以避免悬空指针。 _inner
是 _outer
的组合,而 Outer._member
是一个对象成员,因此它总是被初始化,因此这里应该满足这个条件。
现在我当然不需要 Outer
可以复制了。即使是可移动的也不一定是必需的,因为它不应该在一切都设置好之后移动——尽管在构建其他对象时让它移动会很方便。无论如何,当移动 Outer
时,这种设计 _inner._ptr_to_outer_member
就会失效。 - 这可以通过禁用 Outer
的复制和移动操作来防止,但感觉它只是在解决一个不好的做法。有一条指南说要避免存储对堆栈分配变量的引用,在我看来,这种情况可能有类似的原因要避免。
我能看到的另一种方法是像这样堆分配 _member
(版本 2):
class Outer {
std::unique_ptr<Some_type> _member;
Inner _inner;
};
class Inner {
Some_type* _ptr_to_outer_member;
};
Outer
现在是可移动且不可复制的,这正是我想要的,但这是为了堆分配 _member
的成本,这违反了关于首选对象成员的准则。
这让我想到了一个问题:是否有任何关于此的指南?我的意思是当您想共享对象时(如设计版本 2)使用指向对象的指针作为成员——这对我来说可能最有意义(除非我遗漏了什么)。或者只是禁用此类对象的移动(如设计版本 1 中所示)并按其不可移动的方式处理它。
或者这些例子有什么根本性的错误吗?
在此先感谢您对此的任何见解。
有一些准则,但它们是通用的,例如 use unique_ptr
for owning pointers。
其余的准则是:保持简单。它是如何以这种相互依赖的方式进入这个层次结构的?有没有更好的方法来构建您的应用程序?
双向引用是可以的,但需要自己维护,或者Outer
不可复制不可移动:
class Outer {
Some_type member;
std::unique_ptr<Inner> inner;
public:
Outer() {}
Outer(Outer const& that) = delete; // makes Outer not copyable/movable
Outer& operator=(Outer const& that) = delete;
/* The only way to update inner, also sets the back-reference */
void setInner(std::unique_ptr<Inner> ptr) noexcept {
inner = std::move(ptr);
if (inner) {
inner->ptr_to_outer_member = &member;
}
}
};
如果 Outer
是可移动的,它应该主动维护所有对自身的反向引用以保持它们同步。
例如像这样:
class Some_type {
};
class Inner {
Some_type* ptr_to_outer_member;
friend class Outer; // so that Outer may reach the private ptr_to_outer_member
};
class Outer {
Some_type member;
std::unique_ptr<Inner> inner; // owning pointer to Inner
public:
Outer() noexcept {
}
Outer(Outer&& that) noexcept {
*this = std::move(that);
}
Outer& operator=(Outer&& that) noexcept {
member = std::move(that.member);
setInner(std::move(that.inner));
return *this;
}
/* The only way to update inner, also sets the back-reference */
void setInner(std::unique_ptr<Inner> ptr) noexcept {
inner = std::move(ptr);
if (inner) {
inner->ptr_to_outer_member = &member;
}
}
};
int main() {
Outer o;
o.setInner(std::make_unique<Inner>());
Outer o2(std::move(o)); // OK, move-construction
Outer o3;
o3 = std::move(o2); // OK, move-assignment
Outer o4(o3); // error: Outer is not copyable
}
在可移动类型中使用反向引用几乎总是需要自定义 copy/move-constructor。这意味着我们还必须牢记 rule of 3/5/0 。在此示例中,unique_ptr
使我们免于拥有自定义析构函数。情况可能并非总是如此。
我是 C++ 的新手(具有 Java 背景),我正在尝试了解 class 设计的原则和实践。
根据我已经阅读的内容,在我看来,人们应该更喜欢将 class 成员作为对象而不是将它们作为指针(或引用)。 - 如果这有什么不对,请纠正我。
基于此,这是我的设计(版本 1):
class Outer {
Some_type _member;
Inner _inner;
};
class Inner {
Some_type* _ptr_to_outer_member;
};
我知道需要保证引用对象 (Outer._member)
的生命周期超过引用对象 (Inner)
及其指针 _ptr_to_outer_member
的生命周期,以避免悬空指针。 _inner
是 _outer
的组合,而 Outer._member
是一个对象成员,因此它总是被初始化,因此这里应该满足这个条件。
现在我当然不需要 Outer
可以复制了。即使是可移动的也不一定是必需的,因为它不应该在一切都设置好之后移动——尽管在构建其他对象时让它移动会很方便。无论如何,当移动 Outer
时,这种设计 _inner._ptr_to_outer_member
就会失效。 - 这可以通过禁用 Outer
的复制和移动操作来防止,但感觉它只是在解决一个不好的做法。有一条指南说要避免存储对堆栈分配变量的引用,在我看来,这种情况可能有类似的原因要避免。
我能看到的另一种方法是像这样堆分配 _member
(版本 2):
class Outer {
std::unique_ptr<Some_type> _member;
Inner _inner;
};
class Inner {
Some_type* _ptr_to_outer_member;
};
Outer
现在是可移动且不可复制的,这正是我想要的,但这是为了堆分配 _member
的成本,这违反了关于首选对象成员的准则。
这让我想到了一个问题:是否有任何关于此的指南?我的意思是当您想共享对象时(如设计版本 2)使用指向对象的指针作为成员——这对我来说可能最有意义(除非我遗漏了什么)。或者只是禁用此类对象的移动(如设计版本 1 中所示)并按其不可移动的方式处理它。
或者这些例子有什么根本性的错误吗?
在此先感谢您对此的任何见解。
有一些准则,但它们是通用的,例如 use unique_ptr
for owning pointers。
其余的准则是:保持简单。它是如何以这种相互依赖的方式进入这个层次结构的?有没有更好的方法来构建您的应用程序?
双向引用是可以的,但需要自己维护,或者Outer
不可复制不可移动:
class Outer {
Some_type member;
std::unique_ptr<Inner> inner;
public:
Outer() {}
Outer(Outer const& that) = delete; // makes Outer not copyable/movable
Outer& operator=(Outer const& that) = delete;
/* The only way to update inner, also sets the back-reference */
void setInner(std::unique_ptr<Inner> ptr) noexcept {
inner = std::move(ptr);
if (inner) {
inner->ptr_to_outer_member = &member;
}
}
};
如果 Outer
是可移动的,它应该主动维护所有对自身的反向引用以保持它们同步。
例如像这样:
class Some_type {
};
class Inner {
Some_type* ptr_to_outer_member;
friend class Outer; // so that Outer may reach the private ptr_to_outer_member
};
class Outer {
Some_type member;
std::unique_ptr<Inner> inner; // owning pointer to Inner
public:
Outer() noexcept {
}
Outer(Outer&& that) noexcept {
*this = std::move(that);
}
Outer& operator=(Outer&& that) noexcept {
member = std::move(that.member);
setInner(std::move(that.inner));
return *this;
}
/* The only way to update inner, also sets the back-reference */
void setInner(std::unique_ptr<Inner> ptr) noexcept {
inner = std::move(ptr);
if (inner) {
inner->ptr_to_outer_member = &member;
}
}
};
int main() {
Outer o;
o.setInner(std::make_unique<Inner>());
Outer o2(std::move(o)); // OK, move-construction
Outer o3;
o3 = std::move(o2); // OK, move-assignment
Outer o4(o3); // error: Outer is not copyable
}
在可移动类型中使用反向引用几乎总是需要自定义 copy/move-constructor。这意味着我们还必须牢记 rule of 3/5/0 。在此示例中,unique_ptr
使我们免于拥有自定义析构函数。情况可能并非总是如此。