封装 vs 结构体 - 这被认为是不好的编程风格吗?

4

我有很多在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;
}
DeviceVectorthrust::device_vector的一个包装器,::iterator可以转换为原始设备指针。这是必需的,因为需要调用自定义内核并需要对设备内存进行处理。

我很关心封装性,但是:

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

因此,通过使用结构体,可以使生活变得更加简单。

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

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

我想知道在封装的意义上,这是否实际上是等效的。如果是,整个概念是否存在任何问题,并可能在某些时候产生影响?


4
C++中类(class)和结构体(struct)唯一的区别是默认可见性(class为private,struct为public)。如果您不指定任何构造函数,编译器仍会给您提供默认构造函数,因此我不确定您在结构体示例中初始化函数的目的是什么。 - jpm
2
你的问题实际上是在问“封装 vs 公共数据”。jpm 关于使用 struct 是正确的。 - Drew Dormann
“class” 和 “struct” 这两个词在面向对象设计中有更强的内涵,这是值得一提的。C++ 是混淆这些术语的罪魁祸首。 :) - Tom Kerr
@jpm: 我知道它们之间的区别,这更多是关于语义学:通常,我避免使用公共类成员,但结构体只是公共成员的分组。当然,你对默认构造函数是正确的,但initialize让我更明显地看到用户必须在创建AB对象后做一些事情 - bbtrb
3个回答

2

简单明了。在需要之前,不要介绍抽象和封装。


2

始终将数据成员设置为私有是一个好习惯。尽管你的结构体可能很小,只有几个成员函数,并且需要暴露数据成员,但随着程序的发展,“结构体”往往会增长和繁殖。在你知道之前,所有的代码都依赖于这些结构体的内部机制,对其进行微小的更改将会影响整个代码库。

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

这需要更多的前期工作,但大多数情况下,长期来看它是值得的。


1

这是一个权衡。

使用值结构体可以是一种美妙简单的方式来组合一堆数据。如果你开始添加很多辅助程序并依赖它们超出了它们的预期用途,它们可能会非常笨拙。严格要求自己何时以及如何使用它们,它们就没问题了。在这些对象上没有任何方法是让自己明显的好方法。

您可能有一些用于解决问题的类集,我称之为模块。在模块内部使用值结构体很容易理解。在模块外部,您必须希望它们能够良好地运行。由于它们没有严格的接口,因此您必须希望编译器会警告您有关误用的信息。

鉴于这种情况,我认为它们更适合匿名或详细命名空间。如果它们最终出现在公共接口中,人们往往会为它们添加一些额外的功能。删除这些额外的功能或将其重构为具有接口的一流对象。

我认为它们更适合作为const对象。您遇到的问题是,在其整个生命周期中,您都在(尝试)维护此“对象”的不变性。如果不同的抽象级别希望它们具有轻微的变异,则进行复制。命名参数惯用语对此很有用。

领域驱动设计对这个主题进行了深思熟虑的处理。它将其描述为更实际的理解和促进设计的方式。

代码整洁之道也讨论了这个话题,但从不同的角度。它更像是一本道德书。

两本书都非常棒,通常在这个主题之外也会推荐。


谢谢你的回答,我对于没有方法的 struct 看起来基本上和 class 相比是一样的想法。将这些 struct 放入命名空间中(或者可能将其可见性限制为仅使用它们的类?)是一个很好的想法。 - bbtrb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接