Singleton and template Error (C++)

Started by
12 comments, last by SeanMiddleditch 8 years, 6 months ago

Hello, First of all thank you for taking time to help.

I recently started playing around with templates and singletons. I'm trying to make something like Unity's Debug.Log but in C++. Starting with simple things like cout. Unfortunately I got stuck and with all the googling I can't seem to find how to solve this issue without not making class singleton. Maybe there is better way of doing this?

//Console.h


#pragma once
#include <iostream>

class Console
{
private:
	Console() {}
	~Console() {}
public:
	static Console& instance()
	{
		static Console INSTANCE;
		return INSTANCE;
	}

	template <class T>
	void Print(T arg);

};

//Console.cpp


#include "Console.h"

template <class T>
void Console::Print(T arg)
{
	std::cout << arg.c_str() << std::endl;
}

//main.cpp


#include "Console.h"
int main(int argc, char *argv[])
{
	Console::instance().Print(5);
	return 0;
}

The error is:

Error 1 error LNK2019: unresolved external symbol "public: void __thiscall Console::Print<int>(int)" (??$Print@H@Console@@QAEXH@Z) referenced in function _main E:\WORK\InLine2D\InLine2D\InLine2D\main.obj InLine2D
Error 2 error LNK1120: 1 unresolved externals E:\WORK\InLine2D\InLine2D\Debug\InLine2D.exe 1 1 InLine2D
Advertisement
Unless you explicitly list out all template instantiations, you cannot separate the template into header and source files.

See also: Why can’t I separate the definition of my templates class from its declaration and put it inside a .cpp file?


That aside, you shouldn't use globals because blah blah blah.

EDIT:
SeanMiddleditch has a valid point

Unless you explicitly list out all template instantiations, you cannot separate the template into header and source files.

See also: http://www.parashift.com/c++-faq/templates-defn-vs-decl.html

Thank you, this makes much more sense now. I swear I had it in one place at one point, but I was getting other errors and thought it was because of that. Just changed it to one file and it's all working now. It's my first time using templates by the way, trying to learn more C++. Thank you.

That aside, you shouldn't use globals because blah blah blah.


I rather encourage (careful) use of them. A console is one of the textbook examples I use to illustrate why. (Do you really want to have to pass around an instance handle to every single last place that might print to the console? What about temporary debug code inside of math routines? Gross.)

Having had to deal with an engine that went to great lengths to avoid globals... trust me, there are problems that are solved better by singletons and globals than not. It's all about measured engineering decisions; there is no easy Golden Rule to software design; if there was, we'd all get paid way less. smile.png

Sean Middleditch – Game Systems Engineer – Join my team!



The singleton, a type that can only have one instance ever created, where attempting to create more than one is forbidden by code, that is something to avoid. Even on consoles still a terrible idea. Even for logging where some people suggest it is good, it is a terrible idea: I've worked on plenty of projects where I wanted a private logging system.


A well-known instance, sometimes implemented as pointers within a globally readable data structure, is sometimes a compromise made in coding design.

These well-known instances are NOT singletons (you can create multiple instances if needed) and they are not globals (statically allocated objects), but they are dynamically allocated objects with well-known pointer locations, and they come with strict rules about who can use them for what. These also need to have carefully reviewed, vigorously enforced policies to ensure they are not misused, treating them as short-lived constants in almost all places, with well-defined times of when they can change, such as outside of Update() loops.

Sometimes these well-known instances are managed through a single global object that is nothing more than a bunch of pointers. So you could access it through ::Systems.audio->method(), but there are some big concerns about it. If you are careful that they are mutable and immutable at the right times -- that is they are not modified by using them and that they are only modified at specific times, then it can sometimes work out.

The worst bugs are the ones where those values are modified at the wrong time or by systems unknown. Without warning the behavior of the game is suddenly different, and you cannot find who modified what, when, or why. Suddenly a state was set or a value was modified, and you don't know which of the many systems that could have modified it did so in the wrong spot. Those are nightmares, and the reason globals and mutable shared state are considered terrible design generally.

These well-known instances are NOT singletons


I appreciate the distinction and I agree with frob.

I'm used to using "singleton" in a sense that is probably not the same as what the Gang of Four originally described it as. smile.png

See the service locator pattern (also also here) for a better description of what I usually call a "singleton" and what (I think) frob is talking about.

Sean Middleditch – Game Systems Engineer – Join my team!

For further example, the "singletons" I prefer look something like this:

class IResourceMgr {
public:
  virtual ~IResourceMgr() = default;

  static IResourceMgr& Instance();
  static void ResetInstance(std::unique_ptr<IResourceMgr> instance = nullptr);

  virtual Load() = 0;
  virtual Foo() = 0;
  virtual Bar() = 0;
};
Most user code accesses the above just like the regular singleton pattern prescribes (hence why I call it such), but you can still control the lifetime.

You can pass around any sort of IResourceMgr as you see fit (if you need local temporary, for example), you are still in absolute control of lifetime, you can override the implementation for mocks and testing, and you can select to use a stack-based approach (PushInstance and PopInstance, roughly) when it makes sense to do so.

Sean Middleditch – Game Systems Engineer – Join my team!

Just be aware, service locators and well-known instances can be a source of nasty bugs and serious design flaws.

The pattern introduces quite a lot of coupling. The coupling makes it more difficult to reuse code, where passing a parameter would have allowed reuse. The coupling has a high risk of violating several SOLID principles, it generally breaks Open/Closed, and has a high risk of breaking interface segregation and dependency inversion, but smarter developers will follow them by policy.

The key feature that absolutely must be followed if you want to preserve your sanity is that their behavior and use must be immutable. Any of the services being referenced must not be liable to change. They absolutely cannot change while they are in use. Functionality they control should be gated (both by policy and code rules) to prevent inappropriate changes.

It is generally best to pass parameters directly to methods rather than rely on these tools. There are usually better design decisions away from the patterns but those better solutions require more parameters, more thought, more insight, and more time to implement.

Again, the service locator / well-known instance pattern is NOT A GOOD DESIGN generally. It is an unfortunate compromise taken when design is bad, when other programmers incorrectly coupled features, and when you cannot take the time to break the couplings properly. It should not be the first tool you reach for. It is an unfortunate crutch, a compromise made due to tight deadlines and the sad reality that too much code, both design and implementation, seriously stinks.

It is not a thing to be proud of. It is a thing that you are best removing from the code as quickly as possible. It is a code smell, a technical debt, not a point of glory.

It is not a thing to be proud of. It is a thing that you are best removing from the code as quickly as possible. It is a code smell, a technical debt, not a point of glory.


And here's where I disagree. That kind of purity sounds nice on paper but is academic and just doesn't work in practice. Down that path lies passing 60 parameters to constructors and grotesque contortions just to avoid using a global here and there. All of your code because harder to read, harder to understand, and harder to maintain.

Software inherently relies on layered services. Your OS syscalls are, for all intents and purposes, services/singletons. Your hardware is a service. When you build layered software you add more of your own. There's a point at which software design and implementation simply gets more manageable when you assume layer A sits on top of layer B and don't force a decoupling where there _is_ an intrinsic and unavoidable coupling; good use of globals/singletons/services can accomplish that with no _actual_ problems (despite all the hypothetical ones you might come up with).

Dogmatic application of pithy principles gets you nowhere. At least, nowhere you want to be. I've been there. smile.png

Sean Middleditch – Game Systems Engineer – Join my team!

I have to agree with Sean here, I am seriously tired of dealing with the cognitive overhead of dependency injection frameworks that have me forced to pass the same damn object around everywhere because it's sufficiently basic and low-level that everything needs it, and at the end of the day it's still a global because everything everywhere has a reference to the exact same object. Things are just as bad as with a global, except now I have a dependency injection framework with it and my code's simple logic is obscured by a weird meta-language to describe its relationship with other simple dependencies (parameter-passing in constructors/factories, annotations, XML dependency trees; pick your poison). I do not see a problem with having genuine services as long as their interface is sane, nor am I against explicitly codifying abstract dependencies when it makes sense and adds value. I have a brain and my colleagues do as well, and at work I find that most of it is being used working with against "modern" design patterns and contorting straightforward code to fit someone else's idea of "decoupled code" instead of actually implementing business logic, and probably producing far more bugs as a result. As everything else in life, design principles become bad and dangerous when taken in excess. If in doubt, stop and think.

Anyway frob I found it interesting that you used the word "generally" followed by an unconditional statement. What in your opinion would be an uncommon use case where a singleton or service locator would be a sensible design choice?

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

This topic is closed to new replies.

Advertisement