User: Password:
|
|
Subscribe / Log in / New account

C vs C++

C vs C++

Posted May 20, 2014 11:14 UTC (Tue) by MrWim (subscriber, #47432)
In reply to: C vs C++ by rvfh
Parent article: Clasen: Introducing GtkInspector

Like this:

Foo.h:
class Foo : boost::noncopyable
{
public:
    virtual ~Foo();
    virtual void frob() = 0;
    virtual void frell() = 0;
};

std::shared_ptr<Foo> makeFoo(int some, const std::string& args);

Foo.cpp:

#include "Foo.h"

// Force RTTI information out here so it's not spammed all over the place
Foo::~Foo()
{
}

namespace { // Anon namespace, all symbols confined to this translation unit

struct ConcreteFoo : public Foo
{
    ConcreteFoo() {}
    virtual void frob() {
        printf("frob!\n");
    };
    virtual void frell() {
        printf("frell!\n");
    }
};

std::shared_ptr<Foo> makeFoo(int some, const std::string& args)
{
    return std::make_shared<ConcreteFoo>();
}

It's nice because there is the same number of repetitions of method names as if you were just following the usual pattern of declaration in header file, implementation in cpp file. Also, because it's not a value type, you are likely to be holding it by pointer anyway. If Foo were a pImpl instead you would be dealing with a pointer to a pImpl class which is essentially a pointer anyway.

You can also do things like force management by shared_ptr by changing the factory function. You don't need to faf around with making make_shared a friend or having friend factory functions or anything like that.

Another nice aspect is that you don't need to use private: anywhere. The fact that all the code is in the cpp file offers stronger encapsulation than private:.

The one down-side I'm aware of is that it makes it a little tricker to write white-box tests of the implementation details of the class. The solution I've used in the past is to just #include "Foo.cpp" into my tests. Mostly I prefer black-box testing anyway though so it's not a big deal.


(Log in to post comments)

C vs C++

Posted May 20, 2014 14:26 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

How do you deal with friend classes in this case? "private: friend ...;"? My example is a "pool" being able to have extra access to the objects in it basically to tell the objects information only it could know.

My problem with it is that now you force shared_ptr on it. What if I want a unique_ptr?

C vs C++

Posted May 20, 2014 18:24 UTC (Tue) by MrWim (subscriber, #47432) [Link]

How do you deal with friend classes in this case? "private: friend ...;"? My example is a "pool" being able to have extra access to the objects in it basically to tell the objects information only it could know.
I'm not sure I understand what you're describing, but one way would be to define the pool and the objects in the same cpp file where they can be aware of each other. No friends required ;). Ultimately this technique isn't a panacea and you can apply it where it works and don't where it doesn't. It just seems to me that it's superior to pImpl for most use-cases but seems to be under-used as a technique (unlike pImpl).
My problem with it is that now you force shared_ptr on it. What if I want a unique_ptr?
That was just an example of something that is convenient to do with this approach. Of course if you want a unique_ptr then return a unique_ptr.

C vs C++

Posted May 20, 2014 18:30 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

> I'm not sure I understand what you're describing, but one way would be to define the pool and the objects in the same cpp file where they can be aware of each other.

These are the two largest source files in the repo already :) . There are actually a few things in that class[1] that break pImpl cleanliness anyways: templates which call a private method.

Basically, only the 'pipeline' class knows what the "core frequency" is, so it is locked down to be private and not exported from the library to protect it.

[1]https://github.com/Kitware/sprokit/blob/master/src/sproki...

C vs C++

Posted May 20, 2014 16:53 UTC (Tue) by ehiggs (subscriber, #90713) [Link]

Thanks for sharing this. It's not too bad but I am surprised we would want to have makeFoo return a shared pointer. Why not make it return a raw pointer so the user can determine how to package it? Is this a common practice you see in C++ was just this part of a throwaway example?

Thanks

C vs C++

Posted May 20, 2014 17:49 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

Depends on how opinionated the library is. Ownership tracking of pointers is still a pain in C++ (which Rust finally addresses). Forcing shared_ptr avoids ownership problems at the cost of a reference count (not the worst thing). I just think that unique_ptr should also be an option here at least.

C vs C++

Posted May 20, 2014 17:55 UTC (Tue) by MrWim (subscriber, #47432) [Link]

It's just an example of something that is hard(er) to do otherwise as it can require faffing about with friend classes/functions. As you say, when there's no reason to return a shared_ptr don't. A unique_ptr or an auto_ptr would be just as good in this example, or if you have an aversion to them a raw pointer would also work.

You could even expose a size variable and a placement new factory function for ultimate flexibility, allowing even stack allocation of these objects, but this would almost certainly be over the top.

C vs C++

Posted May 20, 2014 19:22 UTC (Tue) by JGR (subscriber, #93631) [Link]

auto_ptr has nasty and unhelpful copy behaviour which led to it being deprecated, it should be avoided if at all possible IMO.

unique_ptr is generally the most convenient choice; constructing one has zero cost over just returning a pointer to an allocation with new. It's trivial to then move it to a different smart pointer/whatever as necessary, which is much more of a pain with a shared_pointer. Destructing a shared_ptr also uses atomic operations, which is surprisingly expensive.

If you need that level of allocation flexibility and always return a fixed number of (non-polymorphic) object(s), it's usually simpler to just require that the caller pass in a non-const reference to an existing object to fill in.
For the case of 1 object, you could also use a member function or constructor.

C vs C++

Posted May 21, 2014 14:58 UTC (Wed) by mathstuf (subscriber, #69389) [Link]

Well, the problem with unique_ptr is that you need move constructors for it to be fully useful. And that requires C++11 or using boost::move explicitly. Unfortunately, the list of platforms I need to support limit me to shared_ptr since decorating everything with boost::move would likely cause more bugs when it is forgotten.

C vs C++

Posted May 21, 2014 21:07 UTC (Wed) by jwakely (guest, #60262) [Link]

Returning a unique_ptr is better than a raw pointer - it's safe-by-default and also explicit that there is ownership transfer going on. Returning a raw pointer has no advantage over unique_ptr.

If you want to take ownership from a unique_ptr and get a raw pointer that you own it's easy, call release(). You can't do that with a shared_ptr as there could be other owners and there's no way to force them to release their reference.


Copyright © 2017, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds