views:

81

answers:

3

Hello,

I'm trying to call an accessor function in a copy constructor but it's not working. Here's an example of my problem:

A.h

class A {

public:
    //Constructor
    A(int d);
    //Copy Constructor
    A(const A &rhs);

    //accessor for data
    int getData();

    //mutator for data
    void setData(int d);

private:
    int data;
};

A.cpp

#include "A.h"

//Constructor
A::A(int d) {
    this->setData(d);
}

//Copy Constructor
A::A(const A &rhs) {
    this->setData(rhs.getData()); 
}

//accessor for data
int A::getData() {
    return data;
}

//mutator for data
void A::setData(int d) {
    data = d;
}

When I try to compile this, I get the following error:

error: passing 'const A' as 'this' argument of 'int A::getData()' discards qualifiers

If I change rhs.getData() to rhs.data, then the constructor works fine. Am I not allowed to call functions in a copy constructor? Could somebody please tell me what I'm doing wrong?

Thanks,

helixed

+8  A: 

The problem is rhs is declared as const, but getData() isn't, so it could be modifying rhs when you call it even though rhs is supposedly const. As getData() is an accessor, it should be const too:

//accessor for data
int getData() const;
Michael Mrozek
Thanks. -helixed
helixed
+3  A: 

Your "accessor" can only be called on non-const objects, because it isn't marked const. You should declare it:

int getData() const;

Then you're allowed to call it on rhs, which is a const reference.

Steve Jessop
Makes sense. Thanks. -helixed
helixed
A: 

If you insist on having an accessor function in the first place (a lousy idea in general), it should at least be const:

int A::getData() const { 
    return data;
}

This sort of thing is also a prime candidate for being inline, so you might as well define it in the class definition.

Edit: the reason accessors are a bad idea in general is that they're generally a pretty lousy abstraction. In fact, they're not usually an abstraction at all -- they're a dis traction. Consider the ones you posted: neither the accessor nor the mutator does anything useful at all. All your accessor and mutator are doing is producing public data with really ugly syntax. Worse they require that all client code know about the implementation detail that you've used functions to give access to this data.

Years ago I spent a couple of weeks sifting through bunches of code looking at this very situation. My conclusion was pretty simple: around 90-95% of accessors and mutators are just like the ones in the code you posted -- public data with ugly syntax.

That leaves some single-digit percentage that might have done something useful. Of those I sorted through to be certain exactly what they were really doing, most were doing nothing more than creating a variable with a limited range (e.g. an integer between 1 and 100). For those, you'd be better off using a small template for exactly that purpose -- and getting rid of the ugly syntax by using a "setter" named operator= and a getter named operator T for some appropriate base type T (usually int or unsigned int).

That leaves some minuscule percentage where the accessor/mutator were there to enforce something like a relationship between two variables within the class -- and in those cases, it was usually only an accessor, not a mutator (at least directly).

I reiterate, however: those constituted fewer than 1% of all the acessors and mutators I looked at. In fact, nearly the only instances (when you get down to it) were containers that made their current size visible via an accessor. For these, though the exact form is slightly different, I think the same basic idea as above should generally be used: a public object that overloads operator T to give read-only access to the data. The biggest difference is that this template takes an extra parameter for a type that it'll make its friend, so that type will read/write access instead of the read-only granted to the rest of the world. The general idea looks like this:

template <class T, class owner> 
class value { 
     T data;
     friend class owner;
     value &operator=(T const &t) { data = t; }
     value(T const &init) : data(init) {}
public:
     operator T() const { return data; }
};

In reality, the details can (and do) get a bit messy, mostly because the "owner" type usually isn't really a type at all, it's a template itself, so that has to be turned into a template-template parameter. The other detail that would change in a real implementation is that this often includes some range limits as well -- again, not a big deal, but varies with usage.

Jerry Coffin
Lousy idea compared with what? One obvious example of an accessor function of this type is `std::vector<T>::size()`. That doesn't strike me as bad design. It's `SetData` that I'd question, if anything, and admittedly I'd probably not call the getter `getData`.
Steve Jessop
Why is an accessor a bad idea? I'm ensuring the only way somebody can access my data is through my function. Plus, if I were to decide to add something to the code later, like a printout to the standard output stream, then I'd only have to change my code once inside of the accessor function, instead of all of the places where data is accessed.
helixed
@Steve: see edited answer for what I'd consider a better alternative. @helixed: it's a bad idea because your exposing pure implementation, not an abstraction. Your also making client code ugly, unreadable, etc. In this case, you're not even *doing* anything in the accessor or mutator, so all you're really producing is public data is ugly syntax. Again, see above for a better way.
Jerry Coffin
size was somewhat a trick question: sometimes it's a field, but in GCC's implementation of `vector` it isn't, `size()` returns `end() - begin()`. This template gubbins to get "read-only data members" is great unless you *do* decide that the accessors should do something, at which point because you made the interface look like a data member, you are condemned to write a proxy class *per data member* to preserve your abstraction. Since that's 10 times as much boilerpate, you'd better hope that (as in your case) 90% of uses are weak abstractions that will never be anything other than a field.
Steve Jessop
In practice, the problem IMO isn't accessors, it's classes which are defined as a big bag of fields, rather than representing something which actually *does* anything. IMO concentrating on improving accessor boilerplate is a distraction, unless (as you were in your example) you're trying to cut LoC from a code base that's already made those mistakes. Concentrate instead on designing better classes, where there's at least some potential for varying the implementation, and so member functions genuinely make more sense than public data members.
Steve Jessop
@Steve:I have to disagree -- it's not really a question of whether the problem is accessors *or* badly designed classes. *Both* are problems. While it's certainly true that the overall design is generally a bigger problem, the use of accessors (even when more-or-less public data might make sense) is a problem itself.
Jerry Coffin
I genuinely don't see why. You talk about client code being "ugly, unreadable, etc", as if it's a great tragedy that I have to write `vec.size()` instead of `vec.size`. I really don't see that as a major readability issue. Any void member function that's const is an "accessor" of sorts, the question is whether it's accessing something sensible, or whether your interface is a poorly-disguised description of your implementation. I don't think that replacing functions with data member proxy objects does much other than cut boilerplate in the class itself (not the client).
Steve Jessop
@Jerry Sorry, but I still don't understand. How am I exposing pure implementation? By using a accessor and a mutator together, I can control exactly how and when a person can access the data in my class. Say, for instance, my class A had a default constructor. I could add a flag to my called 'isDataSet', which would be set to false in the default constructor. In the mutator, this value is set to true, and in the accessor if isDataSet is false, an exception would be thrown. In this way, I could ensure a programmer would never accidentally read garbage data.
helixed