C++ smart pointers and the Service Locator (anti-?)pattern












0














So I'm working on a game framework and one of the things I need are access to stateful "services" that can be accessed from all over. I was initially going to use the Singleton pattern but decided to try and come up with alternatives to avoid the short comings of that pattern.



So here is what I've got so far.



Main.cpp



#include "Engine.h"
#include "Logger.h"

int main(int argc, char* argv) {
Engine engine;

// old
{
auto logger = new Logger();

engine.provide(logger);
engine.getLoggerOld().log("old TEST");

engine.provide(nullptr);
delete logger;
}
engine.getLoggerOld().log("old TEST");
// old


// new
{
auto splogger = std::make_shared<Logger>();

engine.provide(splogger);
engine.getLoggerNew()->log("new TEST");
}
engine.getLoggerNew()->log("new TEST");
// new

return 0;
}


Engine.h



#pragma once

#include <memory>

#include "ILogger.h"
#include "NullLogger.h"

class Engine {
public:
Engine() {
//old
serviceLogger = &nullLogger;

//new
spLoggerService = std::make_shared<NullLogger>();
}

///////// old
public:
void provide(ILogger* service) {
if (service) {
serviceLogger = service;
} else {
serviceLogger = &nullLogger;
}
}

ILogger& getLoggerOld() const {
return *serviceLogger;
}
private:
ILogger* serviceLogger;
NullLogger nullLogger;

///////// old

///////// new
public:
void provide(std::shared_ptr<ILogger> loggerService) {
wpLoggerService = loggerService;
}

std::shared_ptr<ILogger> getLoggerNew() {
if (wpLoggerService.expired()) {
wpLoggerService = spLoggerService;
}
return wpLoggerService.lock();
}
private:
std::shared_ptr<ILogger> spLoggerService;
std::weak_ptr<ILogger> wpLoggerService;

///////// new
};


I'm a bit unsure on how I should implement the services, in the above example(s) "Engine"(The Service Locator) essentially is designed to never really "own" the services, and instead it only holds a reference to the services, it's then the externally determined if/how/when the services become invalid.



In the example sections labeled "old", these are basically how I saw this pattern implemented elsewhere. I don't like that its left up to how its used to determine if the pointers will be valid or not.



In the example sections labeled "new" I tried to implement the more modern smart pointers, I like the automatic cleanup of the expired references, etc. But I'm still not sure if I like the way the references are handled.



So I guess my questions are:




  1. Does it make sense to give total ownership of the service to the Engine(locator) and allow its life cycle to manage cleanup?

  2. Should I stay with the raw pointer "old" way I found in online examples?

  3. Should I keep going with the "new" smart pointer way?(one thing I'm not a fan of here is needing the if (wpLoggerService.expired()) check as well as the .lock() when accessing a service.(maybe there is a better smart pointer way to avoide this?)


Also if it makes sense for the Engine(Locator) to manage the life-cycle of the services, I was thinking of maybe making the "provide" function similar to that of the std::vector's emplace_back where you would do something like: engine.provide<Logger>(/*ctor args*/) and the .provide<T>() function would instantiate the object internally to prevent any ownership/access to it externally. (This just seems like a more reasonable way to let the Engine control its life-cycle, or is this dumb?)



Disclaimer: New to C++, first time using smart pointers, please be gentle :3









share



























    0














    So I'm working on a game framework and one of the things I need are access to stateful "services" that can be accessed from all over. I was initially going to use the Singleton pattern but decided to try and come up with alternatives to avoid the short comings of that pattern.



    So here is what I've got so far.



    Main.cpp



    #include "Engine.h"
    #include "Logger.h"

    int main(int argc, char* argv) {
    Engine engine;

    // old
    {
    auto logger = new Logger();

    engine.provide(logger);
    engine.getLoggerOld().log("old TEST");

    engine.provide(nullptr);
    delete logger;
    }
    engine.getLoggerOld().log("old TEST");
    // old


    // new
    {
    auto splogger = std::make_shared<Logger>();

    engine.provide(splogger);
    engine.getLoggerNew()->log("new TEST");
    }
    engine.getLoggerNew()->log("new TEST");
    // new

    return 0;
    }


    Engine.h



    #pragma once

    #include <memory>

    #include "ILogger.h"
    #include "NullLogger.h"

    class Engine {
    public:
    Engine() {
    //old
    serviceLogger = &nullLogger;

    //new
    spLoggerService = std::make_shared<NullLogger>();
    }

    ///////// old
    public:
    void provide(ILogger* service) {
    if (service) {
    serviceLogger = service;
    } else {
    serviceLogger = &nullLogger;
    }
    }

    ILogger& getLoggerOld() const {
    return *serviceLogger;
    }
    private:
    ILogger* serviceLogger;
    NullLogger nullLogger;

    ///////// old

    ///////// new
    public:
    void provide(std::shared_ptr<ILogger> loggerService) {
    wpLoggerService = loggerService;
    }

    std::shared_ptr<ILogger> getLoggerNew() {
    if (wpLoggerService.expired()) {
    wpLoggerService = spLoggerService;
    }
    return wpLoggerService.lock();
    }
    private:
    std::shared_ptr<ILogger> spLoggerService;
    std::weak_ptr<ILogger> wpLoggerService;

    ///////// new
    };


    I'm a bit unsure on how I should implement the services, in the above example(s) "Engine"(The Service Locator) essentially is designed to never really "own" the services, and instead it only holds a reference to the services, it's then the externally determined if/how/when the services become invalid.



    In the example sections labeled "old", these are basically how I saw this pattern implemented elsewhere. I don't like that its left up to how its used to determine if the pointers will be valid or not.



    In the example sections labeled "new" I tried to implement the more modern smart pointers, I like the automatic cleanup of the expired references, etc. But I'm still not sure if I like the way the references are handled.



    So I guess my questions are:




    1. Does it make sense to give total ownership of the service to the Engine(locator) and allow its life cycle to manage cleanup?

    2. Should I stay with the raw pointer "old" way I found in online examples?

    3. Should I keep going with the "new" smart pointer way?(one thing I'm not a fan of here is needing the if (wpLoggerService.expired()) check as well as the .lock() when accessing a service.(maybe there is a better smart pointer way to avoide this?)


    Also if it makes sense for the Engine(Locator) to manage the life-cycle of the services, I was thinking of maybe making the "provide" function similar to that of the std::vector's emplace_back where you would do something like: engine.provide<Logger>(/*ctor args*/) and the .provide<T>() function would instantiate the object internally to prevent any ownership/access to it externally. (This just seems like a more reasonable way to let the Engine control its life-cycle, or is this dumb?)



    Disclaimer: New to C++, first time using smart pointers, please be gentle :3









    share

























      0












      0








      0







      So I'm working on a game framework and one of the things I need are access to stateful "services" that can be accessed from all over. I was initially going to use the Singleton pattern but decided to try and come up with alternatives to avoid the short comings of that pattern.



      So here is what I've got so far.



      Main.cpp



      #include "Engine.h"
      #include "Logger.h"

      int main(int argc, char* argv) {
      Engine engine;

      // old
      {
      auto logger = new Logger();

      engine.provide(logger);
      engine.getLoggerOld().log("old TEST");

      engine.provide(nullptr);
      delete logger;
      }
      engine.getLoggerOld().log("old TEST");
      // old


      // new
      {
      auto splogger = std::make_shared<Logger>();

      engine.provide(splogger);
      engine.getLoggerNew()->log("new TEST");
      }
      engine.getLoggerNew()->log("new TEST");
      // new

      return 0;
      }


      Engine.h



      #pragma once

      #include <memory>

      #include "ILogger.h"
      #include "NullLogger.h"

      class Engine {
      public:
      Engine() {
      //old
      serviceLogger = &nullLogger;

      //new
      spLoggerService = std::make_shared<NullLogger>();
      }

      ///////// old
      public:
      void provide(ILogger* service) {
      if (service) {
      serviceLogger = service;
      } else {
      serviceLogger = &nullLogger;
      }
      }

      ILogger& getLoggerOld() const {
      return *serviceLogger;
      }
      private:
      ILogger* serviceLogger;
      NullLogger nullLogger;

      ///////// old

      ///////// new
      public:
      void provide(std::shared_ptr<ILogger> loggerService) {
      wpLoggerService = loggerService;
      }

      std::shared_ptr<ILogger> getLoggerNew() {
      if (wpLoggerService.expired()) {
      wpLoggerService = spLoggerService;
      }
      return wpLoggerService.lock();
      }
      private:
      std::shared_ptr<ILogger> spLoggerService;
      std::weak_ptr<ILogger> wpLoggerService;

      ///////// new
      };


      I'm a bit unsure on how I should implement the services, in the above example(s) "Engine"(The Service Locator) essentially is designed to never really "own" the services, and instead it only holds a reference to the services, it's then the externally determined if/how/when the services become invalid.



      In the example sections labeled "old", these are basically how I saw this pattern implemented elsewhere. I don't like that its left up to how its used to determine if the pointers will be valid or not.



      In the example sections labeled "new" I tried to implement the more modern smart pointers, I like the automatic cleanup of the expired references, etc. But I'm still not sure if I like the way the references are handled.



      So I guess my questions are:




      1. Does it make sense to give total ownership of the service to the Engine(locator) and allow its life cycle to manage cleanup?

      2. Should I stay with the raw pointer "old" way I found in online examples?

      3. Should I keep going with the "new" smart pointer way?(one thing I'm not a fan of here is needing the if (wpLoggerService.expired()) check as well as the .lock() when accessing a service.(maybe there is a better smart pointer way to avoide this?)


      Also if it makes sense for the Engine(Locator) to manage the life-cycle of the services, I was thinking of maybe making the "provide" function similar to that of the std::vector's emplace_back where you would do something like: engine.provide<Logger>(/*ctor args*/) and the .provide<T>() function would instantiate the object internally to prevent any ownership/access to it externally. (This just seems like a more reasonable way to let the Engine control its life-cycle, or is this dumb?)



      Disclaimer: New to C++, first time using smart pointers, please be gentle :3









      share













      So I'm working on a game framework and one of the things I need are access to stateful "services" that can be accessed from all over. I was initially going to use the Singleton pattern but decided to try and come up with alternatives to avoid the short comings of that pattern.



      So here is what I've got so far.



      Main.cpp



      #include "Engine.h"
      #include "Logger.h"

      int main(int argc, char* argv) {
      Engine engine;

      // old
      {
      auto logger = new Logger();

      engine.provide(logger);
      engine.getLoggerOld().log("old TEST");

      engine.provide(nullptr);
      delete logger;
      }
      engine.getLoggerOld().log("old TEST");
      // old


      // new
      {
      auto splogger = std::make_shared<Logger>();

      engine.provide(splogger);
      engine.getLoggerNew()->log("new TEST");
      }
      engine.getLoggerNew()->log("new TEST");
      // new

      return 0;
      }


      Engine.h



      #pragma once

      #include <memory>

      #include "ILogger.h"
      #include "NullLogger.h"

      class Engine {
      public:
      Engine() {
      //old
      serviceLogger = &nullLogger;

      //new
      spLoggerService = std::make_shared<NullLogger>();
      }

      ///////// old
      public:
      void provide(ILogger* service) {
      if (service) {
      serviceLogger = service;
      } else {
      serviceLogger = &nullLogger;
      }
      }

      ILogger& getLoggerOld() const {
      return *serviceLogger;
      }
      private:
      ILogger* serviceLogger;
      NullLogger nullLogger;

      ///////// old

      ///////// new
      public:
      void provide(std::shared_ptr<ILogger> loggerService) {
      wpLoggerService = loggerService;
      }

      std::shared_ptr<ILogger> getLoggerNew() {
      if (wpLoggerService.expired()) {
      wpLoggerService = spLoggerService;
      }
      return wpLoggerService.lock();
      }
      private:
      std::shared_ptr<ILogger> spLoggerService;
      std::weak_ptr<ILogger> wpLoggerService;

      ///////// new
      };


      I'm a bit unsure on how I should implement the services, in the above example(s) "Engine"(The Service Locator) essentially is designed to never really "own" the services, and instead it only holds a reference to the services, it's then the externally determined if/how/when the services become invalid.



      In the example sections labeled "old", these are basically how I saw this pattern implemented elsewhere. I don't like that its left up to how its used to determine if the pointers will be valid or not.



      In the example sections labeled "new" I tried to implement the more modern smart pointers, I like the automatic cleanup of the expired references, etc. But I'm still not sure if I like the way the references are handled.



      So I guess my questions are:




      1. Does it make sense to give total ownership of the service to the Engine(locator) and allow its life cycle to manage cleanup?

      2. Should I stay with the raw pointer "old" way I found in online examples?

      3. Should I keep going with the "new" smart pointer way?(one thing I'm not a fan of here is needing the if (wpLoggerService.expired()) check as well as the .lock() when accessing a service.(maybe there is a better smart pointer way to avoide this?)


      Also if it makes sense for the Engine(Locator) to manage the life-cycle of the services, I was thinking of maybe making the "provide" function similar to that of the std::vector's emplace_back where you would do something like: engine.provide<Logger>(/*ctor args*/) and the .provide<T>() function would instantiate the object internally to prevent any ownership/access to it externally. (This just seems like a more reasonable way to let the Engine control its life-cycle, or is this dumb?)



      Disclaimer: New to C++, first time using smart pointers, please be gentle :3







      c++ design-patterns





      share












      share










      share



      share










      asked 8 mins ago









      Hex Crown

      575




      575



























          active

          oldest

          votes











          Your Answer





          StackExchange.ifUsing("editor", function () {
          return StackExchange.using("mathjaxEditing", function () {
          StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
          StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
          });
          });
          }, "mathjax-editing");

          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "196"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: false,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: null,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210630%2fc-smart-pointers-and-the-service-locator-anti-pattern%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown






























          active

          oldest

          votes













          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes
















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Code Review Stack Exchange!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          Use MathJax to format equations. MathJax reference.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f210630%2fc-smart-pointers-and-the-service-locator-anti-pattern%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Morgemoulin

          Scott Moir

          Souastre