6

I have a class template that needs to be able to compare between two objects, via comparison objects derived from a Compare class I have:

template<typename T>
class Container {
public:
    template<typename A, typename B>
    class Compare {
    public:
        virtual bool eq(const A&, const B&) const = 0;
    };

I provide a default comparison objects, assuming type T has the operator ==:

    template<typename A, typename B>
    class Default : public Compare<A,B> {
    public:
        bool eq(const A& a, const B& b) const { return a==b; }
    };
private:
    Compare<T,T>* comparison_object;
    bool uses_default;
    Container() : comparison_object(new Default<T,T>()), uses_default(true) {}
    Container(Compare<T,T>& cmp) : comparison_object(&cmp), uses_default(false) {}
    ~Container() { if(uses_default) delete comparison_object; }
};

However, when I try to compile this with a custom class that does not have an operator== overload (even if I provide an object derived from Compare):

MyObjCmp moc;
Container<MyObj>(&moc);

The compiler complains that the operator doesn't exist:

error: no match for 'operator==' (operand types are 'const MyObj' and 'const MyObj')

This makes sense, because the Default class still needs to be created, even though I don't need it. But now I need a workaround...

Any ideas?

Dori
  • 1,035
  • 1
  • 12
  • 22
  • `Compare* comparison_object;` It causes memory leak. please use `std::unique_ptr` or `std::shared_ptr`, instead of using `new`/`delete` directly – ikh Nov 28 '14 at 12:25
  • 1
    My destructor takes care of it... I have a "default_created" boolean flag and conditional deletion, don't worry :) – Dori Nov 28 '14 at 12:26
  • @Niall, I'll edit it... I thought it didn't matter – Dori Nov 28 '14 at 12:29
  • @Dori That solution is also bad. If `comparison_object` points other custom `Compare` object, you should free it - so you should check whether `comparison_object` is `nullptr` or not, rather then check boolean flag. Moreover, even if your code is okay, manual `new`/`delete`-ing has lots of danger to mistake something. Although your code is okay now, it'll have bugs someday. – ikh Nov 28 '14 at 12:31
  • @ikh The question isn't tagged C++11, so I wouldn't make a big fuss over C++11 features. –  Nov 28 '14 at 12:32
  • 1
    @remyabel: It's tagged C++, which these days means C++11 (or possibly 14, I'm not sure whether that's officially the standard yet). – Mike Seymour Nov 28 '14 at 12:37
  • @Mike I think C++14 has been ratified, but does that mean C++14 should be assumed? I don't think so. –  Nov 28 '14 at 12:40
  • 1
    @remyabel: I think so, although the best answers will mention when they use new features, and suggest workarounds for those stuck in the past. That's just my opinion though. – Mike Seymour Nov 28 '14 at 12:42
  • This is for a Uni project, and their servers don't even allow c++11 – Dori Nov 28 '14 at 12:44

2 Answers2

4

Rather than a run-time check for a null pointer, you could use a compile-time check for no object:

Container() : comparison_object(new Default<T,T>), uses_default(true) {}
Container(Compare<T,T>& cmp) : comparison_object(&cmp), uses_default(false) {}

The default constructor, and hence Default, will only be instantiated if needed, so there's no error when using the non-default constructor with a type for which Default would fail.

But be careful juggling raw pointers like that, it's a recipe for memory leaks and worse. Don't forget the virtual destructor for Compare and the Rule of Three, and be very careful that a non-default comparator doesn't get unexpectedly destroyed. Better still, use a smart pointer to take care of all that for you.

Community
  • 1
  • 1
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • But I still need to check for NULL in the second constructor, and if need be create a `Default`... back to square one – Dori Nov 28 '14 at 12:34
  • @Dori: Indeed, I forgot to mention that you should take a reference rather than a pointer, to ensure that it can't be null. If you want the default comparator, use the default constructor. – Mike Seymour Nov 28 '14 at 12:35
  • Nope. While compiler reads this line, it'll instantiate `Default`, won't it? – ikh Nov 28 '14 at 12:36
  • @ikh: I don't thinks so, I'm (almost) sure it should only do that if it instantiates the constructor. I'd better check though. – Mike Seymour Nov 28 '14 at 12:38
  • @MikeSeymour Um, [It seems that you're right](http://coliru.stacked-crooked.com/a/8d8d55f3d54736d7), but I get confused - I'll post an answer >o – ikh Nov 28 '14 at 12:45
  • @ikh: Indeed, `Default` is only instantiated if the constructor that uses it is instantiated (to "read" the line, the compiler only needs to know that `Default` is a class template, not to instantiate it). Demo: http://ideone.com/WELh46 – Mike Seymour Nov 28 '14 at 12:48
  • @MikeSeymour So, are you saying a *non-template* constructor of template class starts to be instantiated when it's *really* used? Um.. – ikh Nov 28 '14 at 12:52
  • @ikh: A member function (including a constructor) of a class template is itself a template, only (automatically) instantiated if it's used. – Mike Seymour Nov 28 '14 at 12:54
  • @MikeSeymour `A member function of a class template is itself a template` I've never heard that.. Thank you! – ikh Nov 28 '14 at 12:55
0
template<typename T1, typename T2 >
class Container {
public:
    template<typename T3, typename T4 >
    class Compare {
    public:
        virtual bool eq(const T1&, const T2&) const = 0;
    };


    class Default : public Compare {
    public:
        bool eq(const T1& a, const T2& b) const { return a==b; }
    };

private:
    Compare<T1,T2>* comparison_object;
    bool uses_default;
    Container(Compare<T1,T2>* cmp) : comparison_object(cmp), uses_default(false) {
        if (!cmp) {
            comparison_object = new Default<T,T>();
            uses_default = true;
        }
    }
    ~Container() { if(uses_default) delete comparison_object; }
};
jam
  • 3,640
  • 5
  • 34
  • 50
Steephen
  • 14,645
  • 7
  • 40
  • 47