Fill members of an object with two different functions

1

Imagine that I have an object a (of the Rational type) and a function to fill in that rational (write its numerator and denominator members). Well, I contemplate two options:

MAIN PROGRAM

#include <iostream>
#include "Racional.h"
using namespace std;

int main()
{
  Racional a, b;
  cout << "Sumar dos numeros racionales" << endl;
  cout << "RELLENA PRIMER RACIONAL: " << endl;
  //OPCION A:
  rellenarRacional(a); //Aquí envío el objeto a por referencia
  //OPCION B:
  a.rellenarRacional();  //Aquí aplico la función sobre el objeto a
}

DEFINITION FUNCTION FOR OPTION A:

void rellenarRacional(Racional& r)
{
  cout << "Introduce el numerador del racional: " << endl;
  cin >> r.numerador;
  cout << Introduce el denominador del racional: " << endl;
  cin >> r.denominador;
}

DEFINITION FUNCTION FOR OPTION B:

void Racional::rellenarRacional()
{
  cout << "Introduce el numerador del racional: " << endl;
  cin >> numerador;
  cout << "Introduce el denominador del racional: " << endl;
  cin >> denominador;
}

Could you help me see the differences between the two? When should I use one and when? Because as far as I read, the best option is B in this program.

Thank you and greetings.

    
asked by ProgrammerJr 08.02.2018 в 13:39
source

3 answers

2

Independent function

void rellenarRacional(Racional& r)
{
  cout << "Introduce el numerador del racional: " << endl;
  cin >> r.numerador;
  cout << Introduce el denominador del racional: " << endl;
  cin >> r.denominador;
}

This version has two drawbacks:

  • You can not access protected or private elements of the class (unless you use friend )
  • It can not be overwritten in the case of inheritance (with polymorphism) 1

The main advantage is that it does not overload the class interface.

Member feature

void Racional::rellenarRacional()
{
  cout << "Introduce el numerador del racional: " << endl;
  cin >> numerador;
  cout << "Introduce el denominador del racional: " << endl;
  cin >> denominador;
}

This function is basically the opposite of the previous option:

  • Has unrestricted access to all elements of Racional
  • It can be overwritten in the case of inheritances (with polymorphism)
  • Being a member function joins the list of functions in the class interface

When to choose one or the other?

It depends on each case.

As a general rule we could say that if the functionality to be covered is intrinsic to the object, for example a method that paints a geometric object in a scene, the functionality should be implemented in the class itself. Also, because I am not in favor of using friend except in totally justified cases, if the function needs to access protected or private methods, it is a member function.

On the other hand, if the functionality is accessory or certain design patterns are used (for example, the decorator pattern), it may be interesting to implement independent functions.

But there are no written rules to fire on when to opt for a mechanism and when on the other.

Notes:

  • 1 After reading other published answers, I think it is convenient to explain this point so as not to create confusion. Effectively in C ++ a function can be overloaded as many times as you want:

    void func(int)
    { }
    
    void func(float)
    { }
    

    But these free functions can give problems when working with polymorphism:

    struct POO
    { };
    
    struct POO2 : POO
    { };
    
    void func(POO *)
    { std::cout << "POO\n"; }
    
    void func(POO2 *)
    { std::cout << "POO2\n"; }
    
    int main()
    {
      POO2* poo2 = new POO2;
      POO* poo = poo2;
    
      func(poo);
      func(poo2);
    }
    

    This situation will not occur in the case of member functions as long as they are marked as virtual:

    struct POO
    {
      virtual void func()
      { std::cout << "POO"; }
    };
    
    struct POO2 : POO
    {
      void func() override
      { std::cout << "POO2"; }
    };
    
    int main()
    {
      POO2* poo2 = new POO2;
      POO* poo = poo2;
    
      poo->func();
      poo2->func();
    }
    
answered by 08.02.2018 / 13:51
source
3

It is preferable to use free functions that are not friend (non-member, non-friend) before member functions. (For that matter: your option A)

  

Scott Meyers - Effective C ++ (Chapter 4, Item 23)
  Prefer non-member non-friend functions to member functions

Also, there is a famous previous article, also by Scott Meyers, at link

I think basically the ideas with which this claim is based are the Principles of object orientation that indicate that data should be kept as encapsulated as possible.

The most natural way for the author is to put these "utilities" in the same namespace as the class, with which it is possible to offer them as part of the class interface, and which can be extended by adding other functions to the same namespace, even in separate files, in the same way that it is done in the STL.

    
answered by 08.02.2018 в 14:40
2

To the excellent responses of eferion and asdasdasd I would like to add some more information.

In general terms, it is considered that the use of free functions (your Option A) is a more flexible and generic solution given that you can take advantage of the function overload , the stl from C ++ take advantage of this with std::begin and std::end in the header <iterator> :

std::vector<int>     vi { /* datos */ };
std::list<float>     lf { /* datos */ };
std::map<int, int>  mii { /* datos */ };
int      formacion[100] { /* datos */ };
int formacion2d[10][10] { /* datos */ };

// equivale a vi.begin() el tipo de a es std::vector<int>::iterator
auto a = std::begin(vi);
// equivale a lf.begin() el tipo de b es std::list<float>::iterator
auto b = std::begin(lf);
// equivale a mii.begin() el tipo de c es std::map<int, int>::iterator
auto c = std::begin(mii);
// las formaciones no tienen funciones miembro! el tipo de d es int *
auto d = std::begin(formacion);
// las formaciones no tienen funciones miembro! el tipo de e es int **
auto e = std::begin(formacion2d);

The fundamental types do not have member functions, so it is not possible to obtain an iterator of a 1 formation calling member function begin , since it lacks it:

int formacion[100] { /* datos */ };
auto a = formacion->begin(); // begin no es miembro de int!
auto b = formacion.begin();  // formacion no es un objeto!

It could be said that the free functions (your Option A) is the way that C ++ has to offer a kind of extension methods such as C #, with the difference that they are not invoked as a member function (your Option B) but as a free function. Precisely that is the focus of the technical document N4165 written by Herb Sutter 2 (my translation):

  

Motivation

      1 Generic code .      

It is well known that C ++ has long had the problem of two incompatible function call syntaxes:

     
  • x.f() and x->f() can only be used to call members, such as member functions and callable members; and
  •   
  • f(x) can only be used to call non-members, such as free functions and non-member callable objects.
  •   

Unfortunately, this means that the code must know if the function is a member or not. In particular, this syntactic difference defeats the writing of generic code that must select a certain syntax and therefore does not have a reasonable or direct way to invoke the function f with the object x without knowing in advance if f is or is not a member function for that type. Since there is no single syntax that can invoke both, it is difficult or impossible to write generic code that can be adapted.

     

[...]

      2 non-member, non-friend functions increase encapsulation .      

"Functions want to be free." Scott Meyers and others have observed and taught that it is good to prefer non-member non-member functions since they naturally increase encapsulation. However, current rules disfavor non-member functions because they are visibly different from callers (they have a different call syntax), and are more difficult to find. This proposal would eliminate the major reasons for following this good guide by making non-member functions as easy to use as the member functions [...].

You can read the rest of the document (in English) in the link that I provided before. There it explains in more detail the pros and cons of the calls to free functions (your Option A) and member functions (your Option B), the technical document N4165 was a proposal for C ++ 17 that did not arrive to be introduced, it proposed to unify the two call syntax to be mutually compatible.

Conclusion.

My opinion is that no option is better than the other, you must choose one or the other based on the needs of your project.

  • Also known as an array or in English: array.
  • Prominent C ++ expert, member of the standards committee for more than 10 years and in charge of Visual C ++ at Microsoft since 2002.
  • answered by 08.02.2018 в 16:23