#Using dependency injection over singletons?

59 messages · Page 1 of 1 (latest)

icy bane
#

tl;dr: Trying to figure out how DI works (caused large parameter list) and if it's a good alternative to singletons.

I have a few subsystem or manager type classes which will most likely be one instance and perhaps this is dumb, but I wanna try avoiding singletons (although that's probably the easiest) and I might be wrong, but dependency injection seems to be an alterative. To my understanding instead of having an object accessible anywhere all the time you just pass the instance where needed.

I don't believe I've used DI properly though all I seem to have done is bloat the parameter list for my constructor.

// main function
inputManager = new InputManager();
toolOverlay = new ToolOverlay();
meshManager = new MeshManager();
shaderManager = new ShaderManager();
ui = new UI(rmlContext);
scene = new Scene();

game = new Game(*inputManager, *meshManager, *shaderManager, *ui, *scene);

// game.cpp
Game::Game(InputManager& inputManager, MeshManager& meshManager, ShaderManager& shaderManager, UI& ui, Scene& scene) :
    inputManager(inputManager),
    meshManager(meshManager),
    shaderManager(shaderManager),
    uiManager(ui),
    scene(scene)
{
    AssetHandle suzanne = meshManager.loadMesh("Assets/Meshes/suzanne.obj");
    AssetHandle basic = shaderManager.loadShader("Assets/Shaders/Mesh.vert", "Assets/Shaders/Mesh.frag");

    player = std::make_unique<Player>(suzanne, basic);
    camera = std::make_unique<Camera>(inputManager);
}

For some more context Game originally initialized everything until the thought occurred to me that some things were more engine related and should probably start before the game since the game is what uses it so I moved everything into the main function and attempted to do DI.

ivory gorgeBOT
#

When your question is answered use !solved to mark the question as resolved.

Remember to ask specific questions, provide necessary details, and reduce your question to its simplest form. For tips on how to ask a good question use !howto ask.

shy blade
#

that's honestly not that bloated

#

it's pretty fine

finite wedge
#

its not super bloated but im weary about all that new

#

im kinda against singleton in c++ though

shy blade
#

I was going to get to that

#

Im against globals in C++

#

completely

#

singletons are globals

#

so dont use singletons

#

There's no good reason for it

finite wedge
#

c++ isnt the place for singletons imo. im with caio on that one

#

i use them kinda extensively in java

shy blade
#

(Unless there's some tech debt)

finite wedge
#

but thats about it

shy blade
#

In Java they're somewhat idiomatic (one of the many reasons I've stopped using java)

finite wedge
#

c++ its just a ba ddesign choice

#

iirc its called an anti pattern

shy blade
#

it is

finite wedge
#

i forgot to suggest that

shy blade
#

@icy bane

calm summit
#

specifically their chapters on singleton and service locators

shy blade
#

you're fine with that dependency list

finite wedge
#

thats a really good hit mason

#

karnage gave me that awhile back

shy blade
#
#include <ui/display.h>

int main()
{
    const auto thread_count = std::thread::hardware_concurrency();
    auto thread_pool = std::make_unique<cr::thread_pool>(thread_count == 0 ? 1 : thread_count / 4);

    auto scene = std::make_unique<cr::scene>();

    auto renderer = std::make_unique<cr::renderer>(1024, 1024, 5, &thread_pool, &scene);
    auto main_display = cr::display();

    auto post_processor = std::make_unique<cr::post_processor>();

    auto draft_renderer = std::make_unique<cr::draft_renderer>(1024, 1024, &scene);

    main_display.start(scene, renderer, thread_pool, draft_renderer, post_processor);
}```
finite wedge
#

really nice one

shy blade
#

:)

#

yes, before y'all bash me. I know I dont need the make unique anymore

finite wedge
#

actually i have a mini game i was working on using karnages game engine you can look at

shy blade
#

it's left over from previous iterations

finite wedge
#

to see how we initialize everything

#

it is barebones af though

calm summit
#

there are a lot of ways to realize it

#

passing through the constructor is a very common one

finite wedge
#
namespace {
auto parse_args(int const argc, char const* const* argv) -> std::optional<int> {
    namespace clap = bave::clap;
    auto name = clap::make_app_name(*argv);
    auto description = std::string{"2D platformer game using bave"};
    auto version = bave::to_string(dog_tales::build_version_v);
    auto options = clap::Options{std::move(name), std::move(description), std::move(version)};

    auto show_bave_version = false;
    auto run_tests = false;
    options.flag(show_bave_version, "bave-version", "show bave version");
    options.flag(run_tests, "t,run-tests", "run DogTales tests");

    auto const result = options.parse(argc, argv);
    if (clap::should_quit(result)) { return clap::return_code(result); }

    if (show_bave_version) {
        std::cout << "bave version " << bave::to_string(bave::build_version_v) << "\n";
        return EXIT_SUCCESS;
    }

    if (run_tests) { return test::run_tests(); }

    return {};
}

auto run_app(int const argc, char const* const* argv) -> int {
    auto create_info = bave::DesktopApp::CreateInfo{
        .args = bave::make_args(argc, argv),
        .title = "DogTales",
        .mode = bave::Windowed{.extent = {1280, 720}},
        .assets_patterns = "assets",
    };

    auto app = bave::DesktopApp{create_info};

    app.set_bootloader([](bave::App& app) { return std::make_unique<dog::DogTales>(app); });

    return static_cast<int>(app.run());
}
} // namespace
calm summit
shy blade
#

but TLDR

#

avoid singletons

finite wedge
#

^

shy blade
#

anything is going to be better than them >.>

calm summit
#

generally the point of inversion of control patterns is to allow components to get what they need, but without needing to make them global (or fully specified)

#

the problem is that often requires you to be a lot further along before the problems hit

finite wedge
#

also idk if you're coming from java but i'd generally avoid using new. its relatively fine in that singleton instance, but i'd be careful with it otherwise

#

you can easily cause leaks

calm summit
#

lastly, for C++, I find that some sort of templated meta programming really reduces bloat on things like inversion of control

#

it's very possible to set up infrastructure to make something like:

class SomeThing
    : private Dependencies<InputManager, MeshManager, ShaderManager, UI> // dependencies named once
{
    public:
        using template Dependencies::Dependencies; // reuse services constructor (I don't remember if template is necessary here, I am on a bus)

        void exampleUsage() {
            this->dep<MeshManager>()->loadMesh("Assets/Meshes/suzanne.obj");
        }
};

int main() {
    ServicesRepository repo;
    std::unique_ptr<SomeThing> ptr = repo.create_unique<SomeThing>();
    ptr->exampleUsage();
    std::shared_ptr<InputManager> input = repo.get_shared<InputManager>(); // same object as was constructed for the above
}
#

just work automagically

#

(note also the repository can just instantiate the dependencies, so long as the definitions for the classes are visible, and policies for their construction can be deduced)

#

(it's also possible to make a builder on services repo, or add meta data to the types, to control the instantiation policy of the objects)

#

(IoC containers for languages like C# are pretty wild in their feature sets)

icy bane
#

I tried to understand service locator but that was one of the few things that didn’t make sense to me