C++中指针到指针的替代方案是什么?

6
我发现在我的游戏中有几个地方需要使用指向指针的设计。例如,我有一个名为OpenGLRenderer的类,它会创建网格并给出顶点/索引/纹理坐标数据、材质等属性。接着,有一个名为ResourceManifest的类,它会从文件缓存网格/材质,并在加载这些资源时使用OpenGLRenderer的实例来创建它们的实例。因此,这里存在一种耦合关系。
通常我喜欢在编码时采用RAII设计,这使我想到以下关系:
ResourceManifest(OpenGLRenderer** renderer);

因为当OpenGL上下文已被拆除并且所有OpenGL状态特定的内容需要重新初始化时,例如重新创建窗口时,我只需重新创建OpenGLRenderer并让构造函数/析构函数完成所有工作,而使用它的ResourceManifest将永远不会知道。 我想知道的是,这是否足够证明使用普通的指针对指针,是否有更现代的工具或技术可用? 例如,我一直在研究各种智能指针,但它们无法处理手头的问题,因为我想重新创建托管对象而不传递新的智能指针。

您可以拥有一个指向智能指针的智能指针。 - Ben Voigt
这可能是一个可怕的hack,所以我只作为评论发布;您能否使用placement new和显式的dtor调用重新分配OpenGLRenderer,从而保持内存地址不变?(免责声明:我是一个完全的图形新手) - bitmask
3
参考指针是否太明显了?或者使用包装类型,存储对该包装实例的引用或指针,并让包装器的OpenGLRenderer*成员更新为不同的渲染器。 - Jonathan Wakely
1
给予投票者的建议:请不要认为这是基于个人观点的,我们需要干净的语义。@JonathanWakely 提出的是最佳替代方案。 - πάντα ῥεῖ
1
使用双指针并没有什么完全错误的地方。但是你可以考虑将渲染器封装在RenderingEngine类或类似的类中,然后只需传递对引擎的引用并让它管理渲染器即可。为了避免内存碎片化,应该避免在图形应用程序中不断重建动态结构。或者,您可以考虑使用观察者模式,但我认为这是过度思考了。您仍然是在反向解决问题。请考虑使用场景图。 - paddy
3个回答

3

正如其他人已经说过的,你可以引用一个智能指针。

不过,如果你还想提供比智能指针更多的功能,尤其是当底层数据结构不是同种类型时,你可以将代码变成一个迭代器。这又要看你如何使用指针。然后,无论你最终使用哪个接口,它本身都可以被智能指针包装。


1
这不是一个答案 :-/ 无法评论并不意味着您可以使用答案代替。 - Nikos C.
@NikosC 好的,我不想违反规定或任何事情。如果我不能在答案或评论中发表评论,那么我的唯一选择是获得50个积分吗? - Cos314
@Cos314 我不会在这个问题上投票,但我建议你不要把评论发布为答案,因为这样做无法帮助你达到50的声望。 - paddy
@Cos314 避免抱怨。避免问反问。提出一个编辑,展示你想要说明的替代选项(最好附带一些代码示例)。 - πάντα ῥεῖ
好的。我明白了。我并不想抱怨,如果有这种感觉,我很抱歉。我真的是在问:获得50分是唯一可以添加评论的方式吗?我在思考,也许我可以添加一个只能在某人(更高声誉)批准后发布的评论。 - Cos314
2
我认为迭代器模型并没有涵盖OP(原发布者)的问题,他指出至少有一个共享的OpenGLRenderer - πάντα ῥεῖ

1
我想知道是否有足够的理由使用普通的指针指针,还有其他更现代的工具或技术可用吗?在您特定的场景中,您可以使用类似以下的内容:
  1. ResourceLoader是一个类,其中包含std::map<ResourceId, std::weak_ptr<OpenGLResource> >,其中OpenGL资源是由OpenGL资源使用的通用类。ResourceID是您用于标识特定资源的任何内容(文件名等)。
  2. 当您加载新资源时,ResourceLoader会检查weak_ptr的映射并将其转换为shared_ptr并返回它们。如果映射中没有weak_ptr或者它们为NULL,则创建新的shared_ptr,将其weak_ptr推入映射中,并返回它。

    半C ++伪代码(未经检查,很可能包含拼写错误和语法错误):

      typedef std::shared_ptr<Resource> ResourcePtr;
      typedef std::weak_ptr<Resource> ResourceWeakPtr;
      typedef std::map<ResourceId, ResourceWeakPtr> ResourceMap;
    
      class Loader{
      public:
           ....            
          ResourcePtr loadResource(ResourceId id){
                 ResourceMap::iterator found = resoruceMap.find(id);
                 ResourcePtr result;
                 if ((found == resourceMap.end()) || !(result = found->second.lock())){
                      result = createResource(id);
                      resourceMap.insert(std::make_pair(id, ResourceWeakPtr(result)));
                 }
                 return result;                     
          }
          void reloadAllResources(){
                 for (ResourceMap::iterator i = resourceMap.begin(); i != resourceMap.end(); i++){
                      ResourcePtr cur = i->second.lock();
                      if (cur)
                           cur->reload();
                 }
          }
      protected:
          ResourceMap resourceMap;
          ResourcePtr createResource(ResourceId id){
                return ResourcePtr(new Resource());
          }
      };  
    
  3. 当您想要重新加载丢失的资源时,只需遍历resourceMap,并在每个未过期的对象上调用reload。请参见reloadAllResources

0

我甚至不确定我完全理解你的问题,但让我试着回答一下:你可以使用std::weak_ptr吗?

考虑以下(人为制造的)示例:

#include<memory>
#include<iostream>

class Renderer {
 public:
  Renderer()
      : m_calls(0) { }
  void render() {
    m_calls++;
    std::cout<<"Issued render call #"<<m_calls<<std::endl;
  }
  void reset() {
    std::cout<<"Reset called"<<std::endl;
    m_calls = 0;
  }
 private:
  size_t m_calls;  
};

class Context {
 public:
  Context(std::shared_ptr<Renderer> prenderer)
      : m_prenderer(prenderer) {
  }
  void build_cache() {
    if(auto renderer = m_prenderer.lock()) {
      renderer->render();
    } else {
      std::cout<<"Handle the case when I don't have a renderer to work with"<<std::endl;
    }
  }  
 private:
  std::weak_ptr<Renderer> m_prenderer;  
};


int main() {
  auto renderer = std::make_shared<Renderer>();
  Context ctx(renderer);
  ctx.build_cache();
  ctx.build_cache();
  std::cout<<"Here I reset the renderer"<<std::endl;
  renderer->reset();
  ctx.build_cache();

}

编译为:g++ example.cpp -std=c++11(gcc 4.7.2)。输出:
Issued render call #1
Issued render call #2
Here I reset the renderer
Reset called
Issued render call #1

std::weak_ptr 的目的是共享指针而不共享所有权。因此,您可以重置或完全重新创建您的 Renderer,并且对于 Context 来说都是无关紧要的。此外,将其解引用为空指针具有明确定义的行为。


我认为这并不能解决问题。如果创建了一个新的“Renderer”,那么如何更新“Context::m_prenderer”? - Jonathan Wakely
@JonathanWakely 您说得对 - 我没有回答这个问题 (我尝试重置共享指针,并希望弱指针将解引用到新的共享指针,但显然不起作用)。 换句话说...通过调整它,我正在收敛到"指向指针"的情况,这使我陷入了原来的困境。 哦好吧! - Escualo

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