如何在C ++中存储类成员对象

时间:2013-08-29 23:10:09

标签: c++ c++11 sdl

我正在尝试使用C ++和SDL编写一个简单的游戏。我的问题是,存储类成员变量的最佳实践是什么。

MyObject obj;
MyObject* obj;

我在类似问题中尽可能多地阅读了关于消除指针的内容,但我记得几年前我读过的一些书中他们经常使用它(对于所有非平凡的对象)。另一件事是SDL在其许多函数中返回指针,因此在使用SDL对象时我将不得不使用“*”。

当我认为使用非默认构造函数初始化第一个的唯一方法是通过初始化列表时,我是对的吗?

5 个答案:

答案 0 :(得分:2)

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

  1. 当成员很大时(使用sizeof(MyObject)查找),差异通常与访问无关,而堆栈大小可能是一个问题。
  2. 当对象来自其他来源时,例如,当有工厂函数创建指针时,通常无法存储对象。
  3. 如果对象的动态类型未知,则使用指针通常是唯一的选择。但是,这不应该像往常那样普遍。
  4. 当关系比直接所有者更复杂时,例如,如果在不同对象之间共享对象,则使用指针是最合理的方法。
  5. 在所有这些情况下,您不会直接使用指针,而是使用合适的智能指针。例如,对于1.您可能希望使用std::unique_ptr<MyObject>和4. std::shared_ptr<MyObject>是最佳选择。对于2.您可能需要使用这些智能指针模板中的一个与合适的删除函数相结合来处理相应的清理(例如,从FILE*获得fopen()您使用fclose() {{1}} 1}}作为删除函数;当然,这是一个组成的例子,就像在C ++中一样,你会使用I / O流。)

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

答案 1 :(得分:1)

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

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

此外,如果new很小,使用MyObject会浪费相当多的内存和CPU时间。在典型的现代操作系统中,使用new创建的每个对象的开销大约为16-48字节,因此如果您的对象只是几种简单类型,那么您可能比实际存储有更多的开销。在较大的应用程序中,这可以很容易地增加到很大的数量。当然,对newdelete的调用很可能会花费数百或数千个周期(超出构造函数中使用的时间)。所以,你最终得到的代码运行得更慢并且占用更多内存 - 当然,总是有一些风险让你陷入困境并导致内存泄漏,导致你的程序因内存不足而可能崩溃,当它真的没有出现问题时记忆。

正如那个着名的“墨菲定律”所指出的那样,这些事情必须在最糟糕和最烦人的时候发生 - 当你刚做了一些非常好的工作,或者当你刚刚在一个水平上取得成功时游戏,或者其他什么。因此,尽可能避免这些风险绝对是个好主意。

答案 2 :(得分:0)

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

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

另一种方式是

MyObj foo;
foo.CanDoStuff(stuff);

减少内存管理,但实际上取决于你。

答案 3 :(得分:0)

正如前面的回答声称“除非你必须使用指针”对于通用编程是一个很好的建议,但是有很多问题最终会让你选择指针选择。此外,在您的初始问题中,您没有考虑使用引用的选项。因此,您可以在一个类中面对三种类型的变量成员:

MyObject obj;
MyObject* obj;
MyObject& obj;

我总是考虑引用选项而不是指针一,因为你不需要注意指针是否为NULL。

另外,正如DietmarKühl所指出的,选择指针的一个很好的理由是:

  

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

我认为当你从事一个大项目时,这一点尤为重要。如果你有许多自己的类,安排在许多源文件中,并且你在代码的许多部分使用它们,你会得到很长的编译时间。如果使用普通的类实例(而不是指针或引用),则在重新编译包含此修改类的所有类时,会对类的头文件之一进行简单更改。此问题的一个可能解决方案是使用前向声明的概念,它使用指针引用(您可以找到更多信息{ {3}})。

答案 4 :(得分:0)

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

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