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

C vs C++

C vs C++

Posted May 19, 2014 8:30 UTC (Mon) by rvfh (subscriber, #31018)
In reply to: Clasen: Introducing GtkInspector by brouhaha
Parent article: Clasen: Introducing GtkInspector

Ditto. I must use C at work (and then find memory leaks, understand poorly written code, deal with lack of architecture), but I use only C++ at home.

C++ (as most OO languages I suppose) allows a better representation of the problem, and I find that nice C code usually looks like:

blah_init()
void* handle = blah_create()
int rc = blah_do_something(handle, some data)
blah_destroy(handle)

IOW, people have tried to write OO code with a non-OO language. The Linux kernel is full of this kind of things, not even mentioning structures containing function pointers.

So if what people want is to write OO code, I find it easier to do it with a OO language!


(Log in to post comments)

C vs C++

Posted May 19, 2014 9:53 UTC (Mon) by ehiggs (subscriber, #90713) [Link]

I'm not sure that everyone agrees that using opaque pointers constitutes an attempt to write OO code in C. There's no polymorphism, or inheritance, or other features more commonly known as making up OO. I think you've just given an example of modular programming and I think we can all agree that this is a good approach for a lot of code.

As far as C++ providing a better representation of the problem, it certainly can. However, in your example writing C code in the manner you've shown has less boiler plate than trying to use the pimpl pattern in C++. In the pimpl pattern class definitions are basically doubled, functions have to jump through a proxy.

C vs C++

Posted May 19, 2014 16:27 UTC (Mon) by zlynx (subscriber, #2285) [Link]

Not necessarily. If you are happy having your functions reference data through the pImpl pointer you just have one set of functions and the private data is in the impl struct. You do always need duplicate constructors though, which is annoying.

C vs C++

Posted May 19, 2014 20:28 UTC (Mon) by MrWim (subscriber, #47432) [Link]

I usually prefer to use abstract base classes for data hiding. There is a runtime cost but that is rarely significant. It cuts down on the duplication you usually incur with pImpl and you often want an abstract base class anyway to enable mocking.

Obviously this doesn't apply to value types, but nor does pImpl usually either.

C vs C++

Posted May 20, 2014 7:59 UTC (Tue) by rvfh (subscriber, #31018) [Link]

How do abstract classes solve the issue of 'I want all my private stuff in the cpp file', hidden from view?

C vs C++

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

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.

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