5
\$\begingroup\$

Was trying to implement and example code to smart pointer from link. Is it a good practice? To go with all unique pointers or should we use shared pointers in this case? How can i improve this ?

#include <iostream>
#include <string>
#include<memory>

void LOG(std::string strText)
{
    std::cout << strText << std::endl;
}

class Widget {
public:
    virtual void draw() = 0;
};

class WindowsBtn : public Widget
{
public:
    void draw() {
        LOG("Windows Button");
    }
};

class WindowsMenu : public Widget
{
public:
    void draw() {
        LOG("Windows Menu");
    }
};

class LinuxBtn : public Widget
{
public:
    void draw() {
        LOG("Linux Button");
    }
};

class LinuxMenu : public Widget
{
public:
    void draw() {
        LOG("Linux Menu");
    }
};
/*
class WidgetFactory
{
public:
    virtual Widget* CreateButton() = 0;
    virtual Widget* CreateMenu() = 0;
};

class WindowsFactory : public WidgetFactory{
public:
    Widget* CreateButton() {
        return (new WindowsBtn);
    }

    Widget* CreateMenu() {
        return (new WindowsMenu);
    }

};

class LinuxFactory : public WidgetFactory{
public:
    Widget* CreateButton() {
        return (new LinuxBtn);
    }

    Widget* CreateMenu() {
        return (new LinuxMenu);
    }

};

class Client
{
    WidgetFactory * factory;
public:
    Client(WidgetFactory* factory) :factory(factory)
    {}

    void draw(){
        Widget * wd = factory->CreateButton();
        wd->draw();
        display1();
        display2();
    }
    void display1()
    {
        Widget * w[] = {
            factory->CreateButton(),
            factory->CreateMenu()
        };
        w[0]->draw();
        w[1]->draw();
    }
    void display2()
    {
        Widget * w[] = {
            factory->CreateMenu(),
            factory->CreateButton()
        };
        w[0]->draw();
        w[1]->draw();
    }

};
*/
class SmartWidgetFactory
{
public:
    virtual std::unique_ptr<Widget> CreateButton() = 0;
    virtual std::unique_ptr<Widget> CreateMenu() = 0;
};

class SmartWindowsFactory : public SmartWidgetFactory {
public:
    std::unique_ptr<Widget> CreateButton() {
        return std::make_unique<WindowsBtn>();
    }

    std::unique_ptr<Widget> CreateMenu() {
        return  std::make_unique<WindowsMenu>();
    }
};

class SmartLinuxFactory : public SmartWidgetFactory {
public:
    std::unique_ptr<Widget> CreateButton() {
        return  std::make_unique<LinuxBtn>();
    }

    std::unique_ptr<Widget> CreateMenu() {
        return std::make_unique<LinuxMenu>();
    }
};

class SmartClient
{
    std::unique_ptr<SmartWidgetFactory> factory;
public:
    SmartClient(std::unique_ptr<SmartWidgetFactory> oFactory)
    {
        factory = std::move(oFactory);
    }

    void draw() {
        std::unique_ptr<Widget> wd = factory->CreateButton();
        wd->draw();
        display1();
        display2();
    }
    void display1()
    {
        std::unique_ptr<Widget> w[] = {
            factory->CreateButton(),
            factory->CreateMenu()
        };
        w[0]->draw();
        w[1]->draw();
    }
    void display2()
    {
        std::unique_ptr<Widget> w[] = {
            factory->CreateMenu(),
            factory->CreateButton()
        };
        w[0]->draw();
        w[1]->draw();
    }
};

int _tmain(int argc, _TCHAR* argv[])
{

    /*WidgetFactory *factory;
    factory = new LinuxFactory;
    Client *c = new Client(factory);
    c->draw();*/

    std::unique_ptr< SmartWidgetFactory > oFactory = std::make_unique<SmartLinuxFactory>();
    std::unique_ptr<SmartClient> oClient = std::make_unique<SmartClient>( std::move(oFactory) );
    oClient->draw();

    return 0;
}
\$\endgroup\$
1
  • \$\begingroup\$ just found that the ctor SmartClient(std::unique_ptr<SmartWidgetFactory> oFactory) used value instead of reference. \$\endgroup\$ Commented Feb 14, 2020 at 12:53

3 Answers 3

7
\$\begingroup\$

First, the big bug. Your Widget class has no virtual destructor! This means that every time you return a unique_ptr<Widget>, you're losing the information about what kind of widget needs destroying.

Clang will actually diagnose this mistake for you. No matter what compiler you use, make sure you turn on warnings via -Wall -Wextra (or -W4 on MSVC).


int _tmain(int argc, _TCHAR* argv[])

This is a non-standard main routine. Maybe I'm a POSIX chauvinist, but at least for posting publicly, I'd think it would be more appropriate to use

int main(int, char **)

or even better,

int main()

(since you don't use either of those parameters for anything).

Also notice that I'm using char** instead of char*[], because in C and C++, a function parameter cannot be an array. It is pointless to deceive yourself and mean to deceive others.


SmartClient(std::unique_ptr<SmartWidgetFactory> oFactory)

This constructor should be marked explicit. In fact, every constructor you ever write (except for copy constructors and move constructors) should be explicit. If you leave off the explicit, then you're enabling people to write code like

std::unique_ptr<SmartWidgetFactory> pf = ...;
SmartClient sc = std::move(pf);

or even

SmartClient sc = nullptr;

and it'll compile, and they won't realize what they did wrong until runtime. It's better to catch this kind of thing at compile time. Mark all your constructors explicit.


std::unique_ptr<Widget> CreateButton()

If you intended this function to override the virtual function in the base class, you should have marked it override. (It may still override the virtual function in the base class, even without override, if you spelled everything correctly; but don't bet your workday on it. Mark all overriding functions override and catch your bugs at compile time.)

Also, consider whether it makes conceptual sense that "creating a button" is a non-const method of the factory. Maybe it does; I tend to think it doesn't. So I would write this as

std::unique_ptr<Widget> CreateButton() const override

std::unique_ptr<SmartClient> oClient = std::make_unique<SmartClient>( std::move(oFactory) );

Why does this need to be heap-allocated?

auto oClient = SmartClient(std::move(oFactory));

Even if you want to keep using make_unique for some reason, consider replacing the cumbersome type name std::unique_ptr<SmartClient> with auto:

auto oClient = std::make_unique<SmartClient>(std::move(oFactory));

Notice I've also normalized the whitespace while I'm at it.


void LOG(std::string strText)

Pass large expensive-to-copy objects, such as std::string, by const reference.

void LOG(const std::string& text)

If you are on C++17, consider replacing const std::string& parameters with std::string_view parameters:

void LOG(std::string_view text)

Be aware that you should use string_view only in places where it would make equal sense to use a reference type (so, parameters, and that's it).

Also be aware that when people see LOG("foo") in your code, they will assume that LOG is a macro, because it's ALL UPPER CASE. If you mean for people to assume it's a function, call it print_to_log or something like that.

\$\endgroup\$
4
  • \$\begingroup\$ I know you wrote it for effect, but every constructor you ever write is possibly a bit over-the-top! It's probably a good starting point, of course, and it's a shame that couldn't have been the default, with authors specifying just those cases where implicit conversion should be allowed. \$\endgroup\$ Commented Feb 13, 2020 at 9:49
  • \$\begingroup\$ "every constructor you ever write is possibly a bit over-the-top!" — I would actually bet (a small amount of) money that OP will never need to write an implicit constructor in their life. Relatively few people ever implement std::string(const char*) or BigNum(int), or even Allocator<T>(const Allocator<U>&). We agree it's a good starting point at least. :) \$\endgroup\$ Commented Feb 13, 2020 at 15:10
  • \$\begingroup\$ Well, there's also Size(std::size_t, std::size_t) where adding explicit is noise at best - only constructors that can be called with a single argument actually need explicit. I do agree that compromising readability is less bad than a surprising conversion, though. \$\endgroup\$ Commented Feb 13, 2020 at 15:16
  • \$\begingroup\$ I'd say in that case "adding explicit is noise at worst." In the average case, it's turning new_matrix({x,y}) into a compiler error and forcing the caller to write out new_matrix(Size(x,y)), so it's more apparent to the reader which overload of new_matrix they should be looking for. In the best case godbolt.org/z/XgChsg (I have no idea what's happening here). Btw, I'll add std::true_type() to my list above — I admit I've used std::true_type foo() { return {}; } in preference to auto foo() { return std::true_type{}; } — but again OP won't implement true_type IRL. \$\endgroup\$ Commented Feb 13, 2020 at 15:53
3
\$\begingroup\$

To answer your specific question:

To go with all unique pointers or should we use shared pointers in this case?

You're absolutely correct to return std::unique_ptr values. This indicates that ownership of the pointed-to object is transferred to the caller. If you were to return std::shared_ptr, then that would imply that the factory retained a share of the ownership (e.g. by retaining an object pool, or something).

Remember that once the caller has a unique pointer, it is able to convert it to a shared pointer if it needs to (but the opposite conversion is obviously not possible, except via the raw pointer).

\$\endgroup\$
2
\$\begingroup\$

I've noticed a few small things:

  • You don't need to flush the stream every time you log something. A simple newline will do.

  • A more suitable log stream provided by C++ would be std::clog. You may, however, need to flush this stream.

  • Your formatting is inconsistent. You have spaces in places where you don't have spaces elsewhere, including but not limited to: in template parameters and after return. This makes the code slightly hard to read.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.