封装与结构——这被认为是糟糕的风格吗

Encapsulation vs structs - is this considered bad style?

本文关键字:风格 结构 封装 认为是      更新时间:2023-10-16

我在一个CUDA项目中有一堆类,它们大多是荣耀的struct,并且在组成上相互依赖:

class A {
    public:
        typedef boost::shared_ptr<A> Ptr;
        A(uint n_elements) { ... // allocate element_indices };
        DeviceVector<int>::iterator get_element_indices();
    private:
        DeviceVector<int> element_indices;
}
class B {
    public:
        B(uint n_elements) { 
            ... // initialize members
        };
        A::Ptr get_a();
        DevicePointer<int>::iterator get_other_stuff();
    private:
        A::Ptr a;
        DeviceVector<int> other_stuff;
}

DeviceVector只是thrust::device_vector的包装,并且::iterator可以被强制转换为原始设备指针。这是必要的,因为自定义内核被调用,并且需要设备内存的句柄。

现在,我确实关心封装,但

  • 指向数据的原始指针必须公开,因此使用AB的类可以在GPU上运行自定义内核
  • 不需要默认构造函数,设备内存应自动分配-->shared_ptr<T>
  • 只需要AB上的极少数方法

因此,只需使用structs 就可以让生活变得更简单

struct A {
    void initialize(uint n_elements);
    DeviceVector<int> element_indices;
}
struct B {
    void initialize(uint n_elements);
    A a;
    DeviceVector<int> other_stuff;
}

我想知道我是否正确,从封装的意义上来说,这实际上是等价的。如果是这样的话,整个概念是否有什么问题,可能会在某个时候产生影响?

让它变得简单。在需要之前不要引入抽象和封装。

始终将数据成员设为私有是一个好习惯。一开始,您的结构可能看起来很小,没有或只有几个成员函数,并且需要公开数据成员。然而,随着程序的发展,这些"结构"往往会增长和激增。在您意识到这一点之前,您的所有代码都依赖于其中一个结构的内部,对它的微小更改将在整个代码库中产生反响。

即使您需要公开数据的原始指针,通过getter仍然是一个好主意。您可能想要更改内部处理数据的方式,例如用std::vector替换原始数组。如果您的数据成员是私有的,并且您正在使用getter,那么您可以在不影响任何使用类的代码的情况下执行此操作。此外,getter允许您强制执行constness,并通过返回const指针使特定的数据段只读。

这是一项更多的前期工作,但大多数时候从长远来看是有回报的。

这是一种权衡。

使用值结构是将一堆数据组合在一起的一种非常简单的方法。如果你开始使用大量的辅助程序,并在超出预期用途的情况下依赖它们,它们可能会非常笨拙。严格要求自己何时以及如何使用它们,它们是好的。在这些对象上使用零方法是一个很好的方法,可以让您自己明白这一点。

您可能有一组用于解决问题的类,我将其称为模块。在模块中具有值结构很容易推理。在模块之外,你必须期望良好的行为。它们上没有严格的接口,所以你必须希望编译器会警告你滥用。

鉴于这一说法,我认为它们更适合用于匿名或详细名称空间。如果它们最终出现在公共界面上,人们往往会在其中添加糖。删除sugar或将其重构为具有接口的第一类对象。

我认为它们作为const对象更合适。你陷入的问题是,你(试图)在这个"对象"的整个生命周期中,在任何地方都保持它的不变性。如果不同层次的抽象想要它们有轻微的突变,那么就复制一个。命名参数习惯用法对此很好。

领域驱动设计对该主题进行了深思熟虑、彻底的处理。它的特点是如何理解和促进设计的更实用的感觉。

Clean Code也从不同的角度讨论了这个主题。它更像是一本道德书。

这两本书都是很棒的书,通常都是在这个主题之外推荐的。