2

I am trying to write a simple game using C++ and SDL. My question is, what is the best practice to store class member variables.

MyObject obj;
MyObject* obj;

I read a lot about eliminating pointers as much as possible in similar questions, but I remember that few years back in some books I read they used it a lot (for all non trivial objects) . Another thing is that SDL returns pointers in many of its functions and therefor I would have to use "*" a lot when working with SDL objects.

Also am I right when I think the only way to initialize the first one using other than default constructor is through initializer list?

4

5 回答 5

2

通常,使用值成员优于指针成员。但是,也有一些例外,例如(此列表可能不完整,仅包含我可以立即提出的原因):

  1. 当成员很大时(用于sizeof(MyObject)查找),差异通常对访问无关紧要,并且堆栈大小可能是一个问题。
  2. 当对象来自另一个来源时,例如,当有工厂函数创建指针时,通常没有替代方法来存储对象。
  3. 如果对象的动态类型未知,则使用指针通常是唯一的选择。但是,这不应该像往常那样普遍。
  4. 当存在比直接所有者更复杂的关系时,例如,如果一个对象在不同对象之间共享,则使用指针是最合理的方法。

在所有这些情况下,您不会直接使用指针,而是使用合适的智能指针。例如,对于 1. 您可能想要使用 a std::unique_ptr<MyObject>,对于 4. astd::shared_ptr<MyObject>是最好的选择。对于 2. 您可能需要使用这些智能指针模板中的一个与合适的删除函数来处理适当的清理(例如,对于FILE*fopen()您那里获得的fclose()用作删除函数;当然,这是一个像在 C++ 中一样,无论如何你都会使用 I/O 流)。

通常,我通常在成员初始化器列表中完全初始化我的对象,而与成员的精确表示方式无关。但是,是的,如果您的成员对象需要构造函数参数,则需要从成员初始化器列表中传递这些参数。

于 2013-08-29T23:22:35.550 回答
1

在初始化的情况下,这取决于选项是什么,但是是的,一种常见的方法是使用初始化列表。

“除非必须,否则不要使用指针”一般来说是个好建议。当然,有时您必须这样做 - 例如,当 API 返回对象时!

此外,如果很小,使用new将浪费相当多的内存和 CPU 时间。MyObject在典型的现代操作系统中,使用创建的每个对象new都有大约 16-48 字节的开销,因此如果您的对象只是几个简单类型,那么您的开销很可能比实际存储更多。在更大的应用程序中,这很容易增加一个巨大的数量。当然,对newor的调用delete很可能需要数百或数千个周期(超出构造函数中使用的时间)。因此,您最终会得到运行速度较慢且占用更多内存的代码 - 当然,您总是存在一些风险,即您会搞砸并发生内存泄漏,导致您的程序可能由于内存不足而崩溃,而实际上它并没有用完记忆。

正如著名的“墨菲定律所述”,这些事情必须发生在最糟糕和最烦人的时候——当你刚刚做了一些非常好的工作,或者你刚刚在游戏中取得了成功,或者某物。因此,尽可能避免这些风险绝对是一个好主意。

于 2013-08-29T23:23:50.520 回答
0

首先,我想说我完全同意 Dietmar Kühl 和 Mats Petersson 的回答。但是,您还必须考虑到 SDL 是一个纯 C 库,其中大多数 API 函数期望可以拥有大量数据的结构的 C 指针。所以你不应该在堆栈上分配它们(你应该使用 new 运算符在堆上分配它们)。此外,由于 C 语言不包含智能指针,因此您需要使用 std::unique_ptr::get() 来恢复 std::unique_ptr 拥有的 C 指针,然后再将其发送到 SDL API 函数。这可能非常危险,因为您必须确保在 SDL 使用 C 指针时 std::unique_ptr 不会超出范围(与 std::share_ptr 类似的问题)。否则你会得到段错误,因为 std::unique_ptr 将在 SDL 使用它时删除 C 指针。

每当您需要在 C++ 程序中调用纯 C 库时,我建议使用 RAII。主要思想是您创建一个拥有 C 指针并为您调用 SDL API 函数的小型包装类。然后使用类析构函数删除所有 C 指针。

例子:

class  SDLAudioWrap {
  public:
  SDLAudioWrap() { // constructor
     // allocate SDL_AudioSpec
  }
  ~SDLAudioWrap() { // destructor
     // free SDL_AudioSpec
  }

  // here you wrap all SDL API functions that involve  
  // SDL_AudioSpec and that you will use in your program
  // It is quite simple
  void SDL_do_some_stuff() {
    SDL_do_some_stuff(ptr); // original C function 
                            // SDL_do_some_stuff(SDL_AudioSpec* ptr)
  }
  private: 
  SDL_AudioSpec* ptr;
}

现在您的程序是异常安全的,并且您不会遇到在 SDL 使用它时让智能指针删除您的 C 指针的可能问题。

更新 1:我忘了提到因为 SDL 是一个 C 库,所以您需要一个自定义删除器类才能使用智能指针正确管理其 C 结构。

具体示例:GSL GNU 科学库。集成例程需要分配一个名为“gsl_integration_workspace”的结构。在这种情况下,您可以使用以下代码来确保您的代码是异常安全的

 auto deleter= [](gsl_integration_workspace* ptr) {
   gsl_integration_workspace_free(ptr);
 };
 std::unique_ptr<gsl_integration_workspace, decltype(deleter)> ptr4 (
 gsl_integration_workspace_alloc (2000), deleter);

我更喜欢包装类的另一个原因

于 2013-08-30T05:46:04.867 回答
0

好吧,创建对象比使用指针要好得多,因为它不容易出错。您的代码没有很好地描述它。

MyObj* foo;
foo = new MyObj;
foo->CanDoStuff(stuff);
//Later when foo is not needed
delete foo;

另一种方式是

MyObj foo;
foo.CanDoStuff(stuff);

更少的内存管理,但真的取决于你。

于 2013-08-29T23:15:41.493 回答
0

正如先前的答案所声称的那样,“除非必须,否则不要使用指针”对于一般编程来说是一个很好的建议,但是有很多问题最终可能会让您选择指针选项。此外,在您最初的问题中,您没有考虑使用参考的选项。所以你可以在一个类中面对三种类型的变量成员:

MyObject obj;
MyObject* obj;
MyObject& obj;

我过去总是考虑引用选项而不是指针选项,因为您不需要关心指针是否为 NULL。

此外,正如 Dietmar Kühl 指出的那样,选择指针的一个很好的理由是:

如果对象的动态类型未知,则使用指针通常是唯一的选择。但是,这不应该像往常那样普遍。

我认为当你在做一个大项目时,这一点特别重要。如果您有许多自己的类,排列在许多源文件中,并且您在代码的许多部分中使用它们,那么您会想出很长的编译时间。如果您使用普通的类实例(而不是指针或引用),只需对类的头文件之一进行简单更改,就可以推断出包含此修改后的类的所有类的重新编译。此问题的一种可能解决方案是使用前向声明的概念,它利用指针引用(您可以在此处找到更多信息)。

于 2013-08-30T00:17:56.430 回答