0

My goal is to prevent initializing FibonacciMemoization class except with the FibonacciMemoization::getInstance() function.

Goal:

  • [ ] prevent initializing with FibonacciMemoization* object1 = new FibonacciMemoization();
  • [ ] prevent initializing with FibonacciMemoization object1;

The code

// https://en.cppreference.com/w/cpp/memory/weak_ptr
// https://refactoring.guru/design-patterns/singleton/cpp/example#example-1

#ifndef FIBONACCI_MEMOIZATION_HPP
#define FIBONACCI_MEMOIZATION_HPP

#include <unordered_map>
#include <mutex>

class FibonacciMemoization {
public:
    static std::shared_ptr<FibonacciMemoization> getInstance();

    /**
     * Singleton should not be cloneable.
     * 
     * auto object1 = FibonacciMemoization(); // no matchng constructor for initialization of 'FibonacciMemoization'
     * This line creates an instance of the `FibonacciMemoization` class using its constructor and then assigns this newly created instance to the `object1`.
     * This involves both construction and assignment.
     * 
     * FibonacciMemoization object1;
     * FibonacciMemoization object2 = object1; // function "FibonacciMemoization::FibonacciMemoization(FibonacciMemoization &other)" cannot be referenced -- it is a deleted function
     * This line does not create a new instance of the `FibonacciMemoization` class. 
     * Instead, it creates a new variable `object2` and attempts to initialize it by copying the value of `object1`.
     * Without deleting the copy constructor `object2` would be a separate instance that could behave independently of `object1`.
     * 
     * auto object1 = FibonacciMemoization::getInstance();
     * FibonacciMemoization object2 = *object1; // function "FibonacciMemoization::FibonacciMemoization(FibonacciMemoization &other)" cannot be referenced -- it is a deleted function
     * `object2` would be a separate instance that could behave independently of `object1`.
     */
    FibonacciMemoization(FibonacciMemoization &other) = delete;

    /** 
     * Singleton should not be assignable.
     * auto object1 = FibonacciMemoization();
     * auto object2 = FibonacciMemoization();
     * object2 = object1; // function "FibonacciMemoization::operator=(const FibonacciMemoization &)" cannot be referenced - it is a deleted function
     */
    void operator=(const FibonacciMemoization &) = delete;

    ~FibonacciMemoization();

    unsigned long calculate(unsigned n);
private:
    static std::weak_ptr<FibonacciMemoization> instance;
    static std::mutex mutex;

    FibonacciMemoization();

    int id;
    std::unordered_map<unsigned, unsigned> cache;

    unsigned long fibonacci(unsigned n);
};

#endif

#include "fibonacciMemoization.hpp"
#include <iostream>

FibonacciMemoization::FibonacciMemoization() {
    srand(time(0));
    id = rand();
    std::cout << typeid(*this).name() << " " << __func__ << " " << id << "\n\n";
}

FibonacciMemoization::~FibonacciMemoization() {
    std::cout << typeid(*this).name() << " " << __func__ << " " << id << "\n\n";
}

std::weak_ptr<FibonacciMemoization> FibonacciMemoization::instance;
std::mutex FibonacciMemoization::mutex;

std::shared_ptr<FibonacciMemoization> FibonacciMemoization::getInstance() {
    std::shared_ptr<FibonacciMemoization> sp;

    std::lock_guard<std::mutex> lock(mutex);
    if (instance.expired()) {
        sp = std::make_shared<FibonacciMemoization>();
        instance = sp;
    }

    return instance.lock();
}

unsigned long FibonacciMemoization::calculate(unsigned n) {
    cache.clear();
    return fibonacci(n);
}

unsigned long FibonacciMemoization::fibonacci(unsigned n) {
    if (n < 2)
        return n;

    if (cache.find(n) != cache.end())
        return cache[n];

    unsigned long fib_n = fibonacci(n - 1) + fibonacci(n - 2);
    cache[n] = fib_n;

    return fib_n;
}

The usage code

#include <chrono>
#include <iostream>

#include "fibonacci.cpp"
#include "fibonacciMemoization.hpp"

void fibonacciExample() {
    const auto start = std::chrono::steady_clock::now();
    const auto fb = fibonacci(42);
    const auto end = std::chrono::steady_clock::now();
    const std::chrono::duration<double> elapsed_seconds = end - start;
 
    std::cout << "fibonacci example\n";
    std::cout << "f(42) = " << fb << '\n' << "elapsed time: ";
    std::cout << elapsed_seconds.count() << "s\n\n"; // Before C++20
// std::cout << elapsed_seconds << "\n\n"; // C++20: operator<< chrono::duration
}

void fibonacciMemoizationExample() {
    const auto start = std::chrono::steady_clock::now();

    // example A pf the unexpected usage.
    // FibonacciMemoization object;
    // const auto fb = object.calculate(42);

// example B of the unexpected usage.
// auto object = new FibonacciMemoization();
// const auto fb = object->calculate(42);
    
// the expected usage
auto object = FibonacciMemoization::getInstance();
const auto fb = object->calculate(42);

    const auto end = std::chrono::steady_clock::now();
    const std::chrono::duration<double> elapsed_seconds = end - start;
 
    std::cout << "fibonacci memoization example\n";
    std::cout << "f(42) = " << fb << '\n' << "elapsed time: ";
    std::cout << elapsed_seconds.count() << "s\n\n";

// example B of the unexpected usage.
// need to explicitly call destructor after use due to use of the `new` keyword.
// delete object;
}

The problem is that std::make_shared<FIbonacciMemoization>() require public constructor and destructor.

What I've tried:

  1. set FibonacciMemoization constructor to private.

The expected result: the compiler and run do not generate error.

The actual result: the compiler generate error.

g++ -std=c++11 main.cpp fibonacciMemoization.cpp -o main && ./main
In file included from main.cpp:1:
In file included from ./menu.cpp:3:
./fibonacciexample.cpp:23:26: error: calling a private constructor of class 'FibonacciMemoization'
    FibonacciMemoization object;
                         ^
./fibonacciMemoization.hpp:48:5: note: declared private here
    FibonacciMemoization();
    ^
1 error generated.
In file included from fibonacciMemoization.cpp:1:
In file included from ./fibonacciMemoization.hpp:7:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/unordered_map:523:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__hash_table:25:
In file included from /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/memory:860:
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/shared_ptr.h:294:37: error: calling a private constructor of class 'FibonacciMemoization'
        ::new ((void*)__get_elem()) _Tp(_VSTD::forward<_Args>(__args)...);
                                    ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/shared_ptr.h:953:55: note: in instantiation of function template specialization 'std::__shared_ptr_emplace<FibonacciMemoization, std::allocator<FibonacciMemoization>>::__shared_ptr_emplace<>' requested here
    ::new ((void*)_VSTD::addressof(*__guard.__get())) _ControlBlock(__a, _VSTD::forward<_Args>(__args)...);
                                                      ^
/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1/__memory/shared_ptr.h:962:19: note: in instantiation of function template specialization 'std::allocate_shared<FibonacciMemoization, std::allocator<FibonacciMemoization>, void>' requested here
    return _VSTD::allocate_shared<_Tp>(allocator<_Tp>(), _VSTD::forward<_Args>(__args)...);
                  ^
fibonacciMemoization.cpp:22:19: note: in instantiation of function template specialization 'std::make_shared<FibonacciMemoization, void>' requested here
        sp = std::make_shared<FibonacciMemoization>();
                  ^
./fibonacciMemoization.hpp:48:5: note: declared private here
    FibonacciMemoization();
    ^
1 error generated.
Jason Rich Darmawan
  • 1,607
  • 3
  • 14
  • 31
  • Where is that `FibonacciMemoization object;` line? It is not part of the code you posted – UnholySheep Aug 26 '23 at 15:27
  • *"std::make_shared() require public constructor and destructor."* you might use `std::shared_ptr(new FIbonacciMemoization)` instead. – Jarod42 Aug 26 '23 at 15:31
  • 1
    There once was a blog post, discussing exactly this question: https://devblogs.microsoft.com/oldnewthing/20220721-00/?p=106879 (I recently read it because it is referenced in a recent post, that might also be of interest to you: https://devblogs.microsoft.com/oldnewthing/20230816-00/?p=108608 ) – kaba Aug 26 '23 at 15:32
  • @UnholySheep sorry, I have now added it to `The usage code`. – Jason Rich Darmawan Aug 26 '23 at 15:40
  • @Jarod42 `std::shared_ptr(new FIbonacciMemoization)` with private constructor but generate error with private destructor. – Jason Rich Darmawan Aug 26 '23 at 15:41
  • Why do you make destructor private? – Jarod42 Aug 26 '23 at 15:49
  • @Jarod42 I believe Singleton instance should only be garbage collected by the GC and no code should explicitly able to call `delete object`. – Jason Rich Darmawan Aug 26 '23 at 15:53
  • user cannot delete the instance directly, it is handled by the smart pointer. (BTW static instance would be simpler) – Jarod42 Aug 26 '23 at 15:57
  • @JasonRichDarmawan What does "garbage collection" mean in C++? – Evg Aug 26 '23 at 16:16
  • @Evg automatic instance destructor when no class or variable have strong relationship to that instance? – Jason Rich Darmawan Aug 26 '23 at 16:22
  • @JasonRichDarmawan There is no such thing in C++. – Evg Aug 26 '23 at 16:22
  • @Evg may I know how should I correct my wrong understanding? currently i thought this is a garbage collector -> when FibonacciMemoization instance is out of scope (only weak_ptr have reference to the instance), it will call destructor automatically. – Jason Rich Darmawan Aug 26 '23 at 16:26
  • Looks like you are treating C++ is behaving the same as some other language you learned previously. Do not do that, really start from scratch and learn the concepts of the new language first. – Pepijn Kramer Aug 26 '23 at 16:26
  • The lifetime of objects in C++ is NOT managed by a garbage collector but mainly by [sopes](https://en.cppreference.com/w/cpp/language/scope). Also note that the use of singletons and globals can be very bad for the maintainability of your code – Pepijn Kramer Aug 26 '23 at 16:27
  • Good sources to learn cpp from are : A [recent C++ book](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) or have a go at https://www.learncpp.com/ (that's pretty decent, and pretty up-to-date). For C++ reference material use : [cppreference](https://en.cppreference.com/w/). And after you learned the C++ basics from those sources, look at the [C++ coreguidelines](https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines) regularely to keep up-to-date with the latest guidelines. – Pepijn Kramer Aug 26 '23 at 16:28
  • @JasonRichDarmawan You're talking about automatic variables, for which destructors are called when they go out of scope. This is not garbage collection. Simple example: `void foo() { int* a = new int; }` - after `foo()` returns, `a` will be destroyed (and this does nothing), but `*a` will never be (but it would have been if we had had a GC). – Evg Aug 26 '23 at 16:30
  • @PepijnKramer yes, I learnt weak singleton pattern (destroy the instance if there is no strong reference to the instance) from the Swift language. `the use of singletons and globals can be very bad for the maintainability of your code` I am not sure why Singleton is bad. But I use singleton because I use Use Case Diagram, e.g [MRT Jakarta Navigation Use Case Diagram](https://github.com/jasonrichdarmawan/ProtocolExample/raw/main/assets/MRT%20Jakarta%20Navigation%20Use%20Cases.drawio.png). Without Singleton, I will have to create multiple UseCase Instances. – Jason Rich Darmawan Aug 26 '23 at 17:03
  • Both singletons and globals, kind of break unit-testability. And no you don't have to make multiple instances you can still make an instance of a class (say in main) give it an interface (abstract baseclass) and inject that interface into the classes that need access to the functionality of that class. By doing this in C++ you have full control over the lifetime of the instance (and this is not true when you have multiple static or dynamic libraries in your code.) – Pepijn Kramer Aug 26 '23 at 17:16
  • Some background material : [issues with singletons](https://www.fluentcpp.com/2018/03/06/issues-singletons-signals/) and [static initialization fiasco](https://en.cppreference.com/w/cpp/language/siof). And this [how to use C++ dependency injection (cppcon 2022)](https://www.youtube.com/watch?v=l6Y9PqyK1Mc) – Pepijn Kramer Aug 26 '23 at 17:19
  • For your question on `make_shared` see my other comment. But if your basic interest is in singletons (pros and cons aside for the moment), then my advice would be to disregard refactoring.guru in this case for c++! For a (thread-safe) singleton in c++ you don't need any pointers, neither raw pointers nor smart pointers. Just search for "Meyers Singleton" with your favourite search engine and you're no track. However, you might hit some not-so-helpful pages ... a good hit seems to be: https://laristra.github.io/flecsi/src/developer-guide/patterns/meyers_singleton.html . – kaba Aug 26 '23 at 22:44
  • @PepijnKramer I watched the video you sent. I get the idea why Singleton is bad. 1) usually we put dependencies inside the `static Singleton& getInstance()` function. 2) usually we initialize dependencies inside a class `auto a = Singleton::getInstance()`. To solve it, I propose 1) using Factory pattern to initialize the singleton instance. 2) by injecting the factory to the constructor and call `factory.getSingleton()` which will a) check if singleton is currently initialized b) if not, initialize the singleton with all of its dependencies. Please let me know if this is stupid. – Jason Rich Darmawan Aug 27 '23 at 05:31
  • It is not how it is usually done. What I usually do is I make an abstract baseclass with pure virtual methods only (methods + getter), this is C++'s way of modeling an interface. Ceate an implementation class for this. Then I either create an instance of that class directly (or a factory returning a `std::unique_ptr`). And I will inject reference to that interface to the classes that need it. – Pepijn Kramer Aug 27 '23 at 05:47
  • I'll make an example – Pepijn Kramer Aug 27 '23 at 05:52
  • Example here : https://onlinegdb.com/7Kw-bLbox – Pepijn Kramer Aug 27 '23 at 06:08

0 Answers0