Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Unique_ptr question/concern [SOLVED]



  • Hello all,

    My C++ is from the 90's and I'm working to learn all the new stuff doing training, tests etc.

    For my current app project (Qt Desktop) I need a singleton instance of several items that will interface with specific hardware. I'm only going to ask about one of them here as I think the process will apply to the others.

    I need a logger. qDebug() is great but my client wants something more. They intend to provide a network port where some logging data must be sent by my app. This will be part of a process control system. So I've made a start on a Logger class:

    @
    class Logger
    {
    public:
    // Constructor/destructor
    Logger();
    ~Logger();

      // Write to logging interface
      void WriteToLog(QString outputString, bool includeDateTime=true);
    

    };
    @

    I also have a "LoggerProvider" class:

    @ class LoggerProvider final
    {
    public:
    LoggerProvider();
    static Logger& GetLogger();

    private:
      static std::unique_ptr<Logger> m_loggerinstance;
    
      explicit LoggerProvider(const LoggerProvider& rhs) = delete;
      LoggerProvider& operator= (const LoggerProvider& rhs) = delete;
    

    };
    @

    I've setup LoggerProvider so that anything that needs to log can call "GetLogger()" to get the singleton instance. The client has told me they only want a single connection to their logging interface. So my singleton Logger class is supposed to open that connection write and close. I need to make sure there is not another logger trying to do the same a the same time.

    I know I could and probably should have some mutexes or readwrite lockers and I will probably add them but my first approach was to try and create a singleton and just use it everywhere. When I get to multiple threads which will happen I'll add the locking.

    So back to the problem. In my main.cpp I get the first instance of Logger. The code for GetLogger and initializing the static looks like:

    @ // Initialize static logger instance
    std::unique_ptr<Logger> LoggerProvider::m_loggerinstance = nullptr;

    // Call LoggerProvider::GetLogger() to access the global instance
    Logger& LoggerProvider::GetLogger()
    {
    if (m_loggerinstance == nullptr)
    m_loggerinstance = Utils::make_unique<Logger>(true);
    return *m_loggerinstance;
    }@

    And Utils::make_unique() is this code:

    @ template<typename T, typename... Args>
    static std::unique_ptr<T> make_unique(Args&&... args)
    {
    return std::unique_ptr<T> (new T(std::forward<Args>(args)...));
    }@

    To understand and verify if this is working I have some debug output in the constructor of Logger. When the program first starts up I see this:

    @2015-01-14 14:01:01.441UTC -> Construct p3::Logger (0xeb3f10)@

    All good! Made the logger. I see some logging output from other program steps. Also all good:

    @2015-01-14 14:01:01.588UTC -> Calling startup
    2015-01-14 14:01:01.592UTC -> Construct p3::MainView (0xeb46f0)
    2015-01-14 14:01:02.062UTC -> Construct p3::Startup (0xc4fd08)@

    My main ui comes up. I have a button that creates and opens a QDialog derivative for testing one of my devices. On that dialog I have a button that opens a QTCPSocket connection to the device I'm trying to test. After opening the dialog, pressing the button I see this in my logging:

    @2015-01-14 14:01:05.092UTC -> Construct p3::Dialog_SyringeTest (0xf77760)
    2015-01-14 14:01:06.380UTC -> Syringe Syringe1 - Open TCPIP connection to: 10.2.10.235
    2015-01-14 14:01:06.389UTC -> Construct p3::io_BaseDevice (0x3d210e0)
    *** 2015-01-14 14:01:06.393UTC -> Construct p3::Logger (0x3d21104)
    2015-01-14 14:01:06.396UTC -> Construct p3::io_SerialDevice (0x3d210e0)
    2015-01-14 14:01:06.405UTC -> Creating SerialDevice: Syringe1-serialio@

    Note the *** line where another Logger is constructed. I'm trying to understand why/how this can be. When this second logger is created, I'm in the process of creating an io_device (my own class) that manages serial/TCP connections.

    Each of my constructors has logging in it so I can learn/trace what is being created when/where as I get comfortable with the c++11 ways. But the creation of the second Logger is a concern. Again I really can only have one of these. I thought by using the provider system and a unique_ptr that I would only ever have one of these.

    Even though I'm not creating any threads yet myself, I am opening a QTCPSocket as part of the process above to communicate with a device. Is it possible that is setting up a new thread? If so are the unique_ptrs not available across threads?

    When I first saw this behavior I tried a test of keeping track of my own pointer and at roughly the same place something strange happens. Even though my pointer is valid when the QDialog opens and I attempt to open my device connection suddenly I get a crash as if the pointer is no longer valid.

    I first suspected scope but since I have qDebug() output in constructors and destructors of almost everything I would have expected to see that my pointed to instance had destructed but it did not. So I'm not sure why the pointer was not valid. Anyway I'd rather use the provider scheme but I need to understand why/how a second instance is getting created.

    A little help to understand what is going on would be much appreciated.

    Thanks in advance!


  • Moderators

    Hi,

    unique_ptr doesn't guarantee that there will only be one instance. Rather, it guarantees that only one pointer exists for each instance.

    I think your use of LoggerProvider and trying to force std::unique_ptr is making your code overly complex and hard to debug.

    Just use a traditional singleton, and get rid of LoggerProvider:

    @
    class Logger
    {
    public:
    // Always return the same instance of the Logger
    static Logger *instance() {
    if (!m_instance)
    m_instance = new Logger;
    return m_instance;
    }

    private:
    // Prevent anyone else from creating a Logger
    Logger();

    // (Make sure you initialize this to nullptr in a .cpp file somewhere)
    static Logger *m_instance;
    

    };
    @

    [quote]Even though I’m not creating any threads yet myself, I am opening a QTCPSocket as part of the process above to communicate with a device. Is it possible that is setting up a new thread?[/quote]No.

    Even if QTcpSocket creates its own internal thread, that thread won't be accessible to you, and it won't be running your custom code.

    By the way, have you seen Qt's "categorical logging":http://doc.qt.io/qt-5/qloggingcategory.html features?



  • Hi JKSH,

    I have glanced at it and I want to study it further to see if can be warped into my clients desire.

    I have considered trying to set the output of say QDebug or something close to send it to my clients logging port.

    This is a factory floor and they have some central system that tracks and logs data sent to it on the TCP port. Eventually I'll need to supply some keywords.



  • I'll go back to my traditional pointer and singleton management and see if I can get it to work. I think I tend to agree after playing with this that yes you are right... it probably is overly complex AND your point is well taken that it does not guarantee there will not be multiple instances and clearly that's what I'm getting.

    Thanks sir or ma'am!



  • Ok I mangled my singleton manager stuff to follow your example and it does work well. I am curious though with that implementation, if I call "instance()" somewhere at the start say in main when does it get deleted?

    From my tests it doesn't appear to be deleted. I guess as the program exits it gets handled anyway.


  • Moderators

    [quote author="SysTech" date="1421285607"]I am curious though with that implementation, if I call "instance()" somewhere at the start say in main when does it get deleted?[/quote]Good question ;)

    http://sourcemaking.com/design_patterns/to_kill_a_singleton

    [quote author="SysTech" date="1421285607"]I guess as the program exits it gets handled anyway.[/quote]Yeah, that's typically how it's done. Some people don't like it, but I think it's perfectly practical in most cases.

    For academic purposes, you could copy the approach used by QCoreApplication: When it's allocated on the stack (in main() ), it sets the global qApp pointer which lets the rest of the program access that instance. Then, the instance gets destroyed when main() returns.

    [quote author="SysTech" date="1421280383"]Thanks sir or ma'am![/quote]You're welcome! (I'm male, if you're wondering)



  • [quote author="JKSH" date="1421286456"]You're welcome! (I'm male, if you're wondering)[/quote]

    LOL... never can be too sure these days!

    I worked for a very large company for a while and actually never met my director... I'd get these emails from "skiang". Since my director at the time was in Malaysia I thought, ok its some guy... Turns out it was a lady and one day I sent an email to Mr. Skiang.

    Luckily she was cool and didn't take offense!


Log in to reply