Encapsulation in C ++ and C


    Definition


    Encapsulation is a set of tools for controlling access to data or methods that manage that data. A detailed definition of the term “encapsulation” can be found in my previous publication on the Habré at this link . This article focuses on encapsulation examples in C ++ and C.


    Encapsulation in C ++


    By default, in the class ( class), data and methods are private ( private); they can only be read and modified by the class to which they belong. The access level can be changed using the appropriate keywords that C ++ provides.


    Several qualifiers are available in C ++, and they modify data access as follows:


    • public ( public) data - accessible to everyone;
    • protected ( protected) - available only to the class and child classes;
    • private ( private) —available only to the class to which they belong.

    For brevity, only two levels (private and public) will be highlighted in the examples.


    Encapsulation example


    In a class Contact, public variables and methods are accessible from the main program ( main). Private variables and methods can only be read, called, or changed by the class itself.


    #include 
    using namespace std;
    class Contact
    {
        private:
            int mobile_number;           // private variable
            int home_number;             // private variable
        public:
            Contact()                    // constructor
            {
                mobile_number = 12345678;
                home_number = 87654321;
            }
            void print_numbers()
            {
                cout << "Mobile number: " << mobile_number;
                cout << ", home number: " << home_number << endl;
            }
    };
    int main()
    {
        Contact Tony;
        Tony.print_numbers();
        // cout << Tony.mobile_number << endl;
        // will cause compile time error
        return 0;
    }

    An attempt to print or change a private variable mobile_numberfrom the main program ( main) will cause a compilation error because access to private data in the class is limited.


    Encapsulation Violation with Friends (Good Practice)


    In C ++, there is the keyword “friend” ( friend) which allows you to add exceptions to the general rules for accessing data. If a function or class is called a friend ( friend) of the class Contact , they get free access to protected or private data.


    There are two basic rules of friendship - friendship is not inherited and not mutual. Also, the presence of “friends” does not change the level of data security - private data remains private with the exception of “friend”.


    #include 
    using namespace std;
    class Contact
    {
        private:
            int mobile_number;           // private variable
            int home_number;             // private variable
        public:
            Contact()                    // constructor
            {
                mobile_number = 12345678;
                home_number = 87654321;
            }
            // Declaring a global 'friend' function
            friend void print_numbers( Contact some_contact );
    };
    void print_numbers( Contact some_contact )
    {
        cout << "Mobile number: " << some_contact.mobile_number;
        cout << ", home number: " << some_contact.home_number << endl;
    }
    int main()
    {
        Contact Tony;
        print_numbers(Tony);
        return 0;
    }

    In this example, a function print_numbers() is a regular function, not a class method Contact. Declaring a function as a print_numbers()“friend” of a class Contact is the only reason the function print_numbers()has access to private data. If you remove the line with the definition of a friend, the code will not compile.


    Note : it’s best not to abuse friends. Adding a friend should be considered as an exception, not as a general practice.


    Encapsulation Violation with Type Conversion and Pointers (Bad Practice)


    First of all, it's worth noting that using pointers and type conversion in this way is a bad idea. This method does not guarantee the receipt of the necessary data. It is poorly read and poorly maintained. Despite this, he exists.


    C ++ inherited from C many tools, one of which is type conversion ( typecasting). By default, all variables and methods in the class are private. At the same time, the standard level of data access in the ( struct) structure is public. It is possible to create a structure or a fully public class in which the data will be located identically to the data in the class Contactand using type conversion to access private data.


    #include 
    using namespace std;
    class Contact
    {
        private:
            int mobile_number;           // private variable
            int home_number;             // private variable
        public:
            Contact()                    // constructor
            {
                mobile_number = 12345678;
                home_number = 87654321;
            }
            void print_numbers()
            {
                cout << "Mobile number: " << mobile_number;
                cout << ", home number: " << home_number << endl;
            }
    };
    struct Contact_struct
    {
        int mobile_number;
        int home_number;
    };
    int main()
    {
        Contact Tony;
        Contact_struct * structured_Tony;
        Tony.print_numbers();
        structured_Tony = (Contact_struct *) & Tony;
        structured_Tony->mobile_number = 20;
        structured_Tony->home_number = 30;
        Tony.print_numbers();
        return 0;
    }

    Private data has been read and modified due to type conversion


    C encapsulation


    Encapsulation is traditionally considered to be one of the key OOP principles. However, this does not limit the use of this principle in procedurally-oriented languages. In C, encapsulation has been used for a long time, despite the lack of the keywords “private” and “public”.


    Private variables


    In the context of encapsulation, all data in C can be considered public by default. The level of access to variables in structures ( struct) can be changed to private by isolating their definition from the main program. The desired effect can be achieved by using separate header (header, .h) and source (source, .c) files.


    In this example, the structure was defined in a separate source file “private_var.c”. Since initializing the structure in C requires allocating and freeing memory, several helper functions have been added.


    #include "private_var.h"
    #include 
    #include 
    struct Contact
    {
        int mobile_number;
        int home_number;
    };
    struct Contact * create_contact()
    {
        struct Contact * some_contact;
        some_contact = malloc(sizeof(struct Contact));
        some_contact->mobile_number = 12345678;
        some_contact->home_number = 87654321;
        return( some_contact );
    }
    void delete_contact( struct Contact * some_contact )
    {
        free(some_contact);
    }

    In the corresponding header file "private_var.h", the structure Contactwas declared, but its contents remained hidden to the main program.


    #ifndef PRIVATE_VAR
    #define PRIVATE_VAR
    struct Contact;
    struct Contact * create_contact();
    void delete_contact( struct Contact * some_contact );
    #endif /* PRIVATE_VAR */

    Thus, for “main.c” the contents of the structure are unknown and attempts to read or modify private data will cause a compilation error.


    #include "private_var.h"
    #include 
    int main()
    {
        struct Contact * Tony;
        Tony = create_contact();
        // printf( "Mobile number: %d\n", Tony->mobile_number);
        // will cause compile time error
        delete_contact( Tony );
        return 0;
    }

    Accessing private variables with Pointers


    Type conversion can be used to overcome encapsulation in C as well as in C ++, but this approach has already been described. Knowing that in the structure the data are arranged in the order of their declaration, pointers and arithmetic of pointers are suitable for achieving the goal.


    Access to variables in the structure is limited. However, only variables are hidden, not the memory in which the data is stored. Pointers can be considered as a reference to the memory address, and if this memory is available to the program, the data stored in this memory can be read and changed. If the pointer is assigned to the memory in which the structure stores its data - they can be read. Using the same structure definition (the same “.c” and “.h” files) and the modified “main.c” file, access restriction was overcome.


    #include "private_var.h"
    #include 
    int main()
    {
        struct Contact * Tony;
        Tony = create_contact();
        int * mobile_number_is_here = (int *)Tony;
        printf("Mobile number: %d\n", *mobile_number_is_here);
        int * home_number_is_here = mobile_number_is_here + 1;
        *home_number_is_here = 1;
        printf("Modified home number: %d\n", *home_number_is_here);
        delete_contact( Tony );
        return 0;
    }

    The data in the structure has been read and modified


    Private functions


    Functions, being external ( extern) by default, are visible in the entire so-called translation unit ( translation unit). In other words, if several files are compiled together into one object file, any of these files will be able to access any function from any other file. Using the keyword “static” ( static) when creating a function will limit its visibility to the file in which it was defined. Therefore, to ensure the privacy of the function, you need to perform several steps:


    • the function must be declared static ( static) either in the source file (.c) or in the corresponding header file (.h);
    • the function definition must be in a separate source file.

    В данном примере, в файле “private_funct.c”, была определена статическая функция print_numbers(). К слову, функция delete_contact() успешно вызывает print_numbers() поскольку они находятся в одном файле.


    #include "private_funct.h"
    #include 
    #include 
    struct Contact
    {
        int mobile_number;
        int home_number;
    };
    struct Contact * create_contact()
    {
        struct Contact * some_contact;
        some_contact = malloc(sizeof(struct Contact));
        some_contact->mobile_number = 12345678;
        some_contact->home_number = 87654321;
        return( some_contact );
    }
    static void print_numbers( struct Contact * some_contact )
    {
        printf("Mobile number: %d, ", some_contact->mobile_number);
        printf("home number = %d\n", some_contact->home_number);
    }
    void delete_contact( struct Contact * some_contact )
    {
        print_numbers(some_contact);
        free(some_contact);
    }

    В соответствующем заголовочном файле "private_funct.h", print_numbers() была декларирована как статическая функция.


    #ifndef PRIVATE_FUNCT_H
    #define PRIVATE_FUNCT_H
    struct Contact;
    struct Contact * create_contact();
    static void print_numbers( struct Contact * some_contact );
    void delete_contact( struct Contact * my_points );
    #endif /* PRIVATE_FUNCT_H */

    Основная программа, “main.c”, успешно вызывает print_numbers() опосредовательно через delete_contact(), поскольку обе функции находятся в одном документе. Тем не менее, попытка вызвать print_numbers() из основной программы вызовет ошибку.


    #include "private_funct.h"
    #include 
    int main()
    {
        struct Contact * Tony;
        Tony = create_contact();
        // print_numbers( Tony );
        // will cause compile time error
        delete_contact( Tony );
        return 0;
    }

    Получение доступа к приватным функциям


    It is print_numbers()possible to call a function from the main program. To do this, you can use the keyword gotoor pass in a mainpointer to a private function. Both methods require changes either in the source file “private_funct.c”, or directly in the body of the function itself. Since these methods do not bypass encapsulation and cancel it, they are beyond the scope of this article.


    Conclusion


    Encapsulation exists outside of OOP languages. Modern OOP languages ​​make using encapsulation convenient and natural. There are many ways to circumvent encapsulation and avoiding questionable practices will help to preserve it in both C and C ++.


    Also popular now: