4

In C++ let's say I have some class A:

Class A
      {
      int a1, a2, a3;

      void foo();
      }

and I need to use a subset of members (a1, a2) in a member function for a second class B.

What I'm wondering is whether I should define the arguments of B's member function by passing a pointer to A as an argument, or whether I should pass the members of A as arguments. e.g.,

Class B
      {
      int b1, b2;

      void bar(A &a);
      }

or should this be

Class B
      {
      int b1, b2;

      void bar(int a1, int a2);
      }

The latter would seem to minimize having to violate encapsulation, because B can then be largely agnostic about the constituents of A; whereas, in the former case, B would have to know something about the members of A. So it would seem the second implementation would be preferable.

Another reason the first implementation seems problematic to me is that, ideally, I'd like to keep the members (a1, a2, a3) of A protected rather than public. Normally I'd be tempted to make B a friend class of A, but the problem here is that A and B will both each have several derived classes, and since friendship doesn't inherit in C++ that wouldn't seem to be a good solution.

So these reasons would argue for using something like the second definition of B. However, in the actual code I'm dealing with, there are quite a few (about 6 or more) members of A that B will require, so this could get unwieldy. Being able to just pass a pointer to A would seem preferable from a readability standpoint and hide the details of what B.bar() requires from other parts of the code that deal with B.

Perhaps one work around would be to have methods inside A that do something like:A::get_a1() {return this->a1} or something, but I'm also not sure if that's really the correct design solution here.

  • Could A hold an instance of B instead of a1 and a2 individually? So you'd basically refactor both members to a single one that is an instance of B? – null Aug 4 '15 at 17:55
  • check out the observer design pattern,it may be a way you are looking for to allow the classes to pass information about the variables... – Ricko Aug 4 '15 at 18:50
  • 1
    The "right design solution" is going to depend very sensitively on the exact details. I appreciate you've generalized the question, but immediately have a list of questions that can't be answered from this simplified example: what are a1,a2? How do they relate to a3? Why are in they in A, and should they be? – Useless Aug 6 '15 at 13:38
  • This depends on details, but I do not see a problem with a function that has 6 parameters. In your case I think it depends more on the context this is in... Do A&B have a deeper relationship, or are they just two classes where one happens to use the other (think e.g. network lib & use of it)? In that case I would most likely not pass A. If the two have a deeper connection and will only be used together I would see much less of a problem. – Andreas Wallner Apr 23 '17 at 10:12
5

However, in the actual code I'm dealing with, there are quite a few (about 6 or more) members of A that B will require, so this could get unwieldy.

Create a Data Transfer class that holds the members you wish to transfer from class A to class B, and then write your method on class B so that it takes an instance of the new class.

3

I recommend abstracting this up a level, especially if you find you need a1 and a2 in multiple locations. The steps go something like this:

  1. Identify what data the function requires.

  2. Conceptually group the data together into its own unit.

  3. Describe the data. What does this data represent?

  4. Create an interface1 named after the description of the data.

Now you inherit from the interface in class A, and class B's function takes a reference to the new interface.

Benefits:

  1. Loose coupling: you decouple the two classes. If B needs to know about data that A has but it is not appropriate for them to be tightly coupled, the interface adds a layer of abstraction that keeps the design clean (even if a little larger).

  2. Code reuse: if the data in the interface is applicable other places, you can reuse the interface.

  3. If a particular use of the interface lends itself more toward Robert Harvey's suggestion of using a Data Transfer class, you can create a bare-bones implementation of the interface (pure virtual class) and use it. The two classes would then be interchangeable as far as users of the interface are concerned, which adds flexibility.

Code example:

class Foo {
public:
  int getA1() const = 0;
  int getA2() const = 0;
};

class A : public Foo {
  int a1, a2, a3;
public:
  // C++11 adds the "override" keyword.
  int getA1() const override { return a1; }
  int getA2() const override { return a2; }
  int getA3() const { return a3; }
}

class B {
public:
  void foo(const Foo& foo) {
    foo.getA1();
    foo.getA2();
  }
}

b.foo(a);

Note: it may also be valid to have class A contain a Foo via composition rather than implement it via inheritance. This is probably the better solution, but based on the wording of your question I felt the above solution was more appropriate. Here is a code example for completeness:

class Foo {
  int a1, a2;
public:
  int getA1() const { return a1; }
  int getA2() const { return a2; }
};

class A {
  int a3;
  Foo foo;
public:
  const Foo& getFoo() const { return foo; }
  int getA3() const { return a3; }
}

class B {
public:
  void foo(const Foo& foo) {
    foo.getA1();
    foo.getA2();
  }
}

b.foo(a.getFoo());

1 C++ has no interface keyword like certain other languages do. Nor does it have a concept of an abstract or pure virtual class: these concepts only apply to functions in classes. In this context, a C++ interface means a class where all functions are pure virtual. The only exceptions are constructors and the destructor, which cannot be virtual and must exist even if the compiler generates them for you. Also note that the destructor must be declared virtual for any class designed to have subclasses.

  • I think the second solution is preferable to the first: there is nothing to suggest in the question that the A class "is-a" parameter of b. – utnapistim Aug 10 '15 at 13:13
0

If you want to avoid an extra "data transfer class" like suggested by @Robert Harvey, you need to access your member variables by getter and setter functions.
Thus your approach described in your last paragraph

Perhaps one work around would be to have methods inside A that do something like:A::get_a1() {return this->a1} or something, but I'm also not sure if that's really the correct design solution here.

would be the right solution to me.

However, I have created dozens of classes (in C# though, but the underlying problem was the same) with a bunch of member variables in each. I also needed to access these member variables, like you do. You only need to read as far as I can see. I am writing them, too.
My solution was not a pair of get/set function for each member (which means for n members you gotta create 2*n functions), but having only 1 get-function and 1 set-function (or 1 of get/set for each variable type). The choice which variable has to be set is done by passing an integer variable (an Enum, to be exact: PID, parameter ID) with those functions:

int GetValue (EnumPID i_enPID, out int    o_iValue);
int GetValue (EnumPID i_enPID, out double o_dValue);
int GetValue (EnumPID i_enPID, out String o_sValue);  // C#
...
int SetValue (EnumPID i_enPID, int    i_iValue);
int SetValue (EnumPID i_enPID, double i_dValue);
int SetValue (EnumPID i_enPID, String i_sValue);  // C#
...
// the int return value is the result of the function: SUCCESS (=1), failure (= 1001 upwards).
// Actually, it's an Enum in my case, too, (EnumResult)...

The credits for this solution go to my former boss, who showed me this kind of design.
It has been working perfectly in C# since.

  • Your C# solutions seems like a weired one to me if there are no other requirements (like exposing the properties in a UI list, loading from a config file, etc.). C# has a much nicer solution for this problem: Properties -- no functions for you to write, to switches to assign to specific values, no implicit typechecking that you have to write + if these are just some properties I think I would throw on an error... Please don't take this too seriously if you have other requirements that led to this solution, I just think for the OPs it's not a good one. – Andreas Wallner Apr 23 '17 at 10:09
  • @Andreas Wallner: When using properties you have 1 set and 1 get for each value, so there is no much gain. I am using multithreading, so there is some code in each get/set to ensure threadsafe data access. Actually each get calls a common get() where this code is added, so I have it just once per get(), not once per value. – Tobias Knauss Apr 23 '17 at 11:30
  • I would are that there is quite some gain, you don't need any other code than a generic {get;set;}for simple variables. If you solve a different problem with your approach (such a thread safety) then this might be a good solution (as I already said before), it just does not fit well to the OPs question. Thread safe access is a completely different question. OT: shouldn't the GetValue calls have refor out parameters? Strings are immutable, how do you get a value using GetValue? – Andreas Wallner Apr 23 '17 at 14:18
  • @ Andreas Wallner: As long as you're adding good arguments to your comments, I am fine with anything you write. ;-) It has not only thread safety, but also other benefits. E.g. you may prevent values from changing while calculations or other operations are being executed. But you're right, it might be an overkill to the OP's situation. On the other hand I have seen many programs and code snippets rapidly growing in terms of size and complexity, so it may be the solution that he needs one day. / true, GetValue should take a reference. I will add that. – Tobias Knauss Apr 23 '17 at 16:22
  • We might just have to disagree ;-) Or not...I simply do not know enough about your application to say what's right or wrong (If there even is such a thing...) but if in doubt I would choose an approach with properties. Regardless, it's hard to get right in any case (like not forgetting to return copies of strings instead of real refs, etc.) – Andreas Wallner Apr 23 '17 at 20:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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