Encapsulation is a set of tools for managing access to the data or methods that manage this data. With the detailed definition of the term “encapsulation” can be found in my previous publication on Habré on this link . This article focuses on examples of encapsulation in C ++ and C.
By default, in a 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 provided by C ++.
In C ++, several qualifiers are available, and they modify data access as follows:
public
) data - accessible to all;protected
) - available only to the class and child classes;For brevity, only two levels (private and public) will be covered in the examples.
In the Contact
class, public variables and methods are accessible from the main program ( main
). Private variables and methods can be read, called, or modified only by the class itself.
#include <iostream> 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; }
Attempting to print or change the private variable mobile_number
from the main program ( main
) will cause a compilation error because access to private data in the class is restricted.
In C ++ there is a keyword “friend” ( friend
) which allows you to add exceptions to the general rules of data access. If a function or class is called a friend of the Contact
class, 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 <iostream> 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, the print_numbers()
function is a regular function, not a class method of Contact
. The declaration of the print_numbers()
function print_numbers()
“friend” of the Contact
class is the only reason that the print_numbers()
function has access to private data. If you remove the string with the definition of a friend - the code will not compile.
Note : it is better not to abuse friends. Adding a friend should be considered as an exception, not as a general practice.
First of all, it is worth noting that using pointers and type conversion in this way is a bad idea. This method does not guarantee obtaining the necessary data. It is poorly read and poorly maintained. Despite this, it exists.
C ++ inherited from C a set of 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 structure ( struct
) is public. It is possible to create a structure or a fully public class in which the data will be located identical to the data in the Contact
class and using type conversion to access private data.
#include <iostream> 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
Traditionally, encapsulation is considered to be one of the key OOP principles. However, this does not limit the use of this principle in procedural-oriented languages. In C, encapsulation has been used for a long time, despite the lack of the keywords “private” and “public”.
In the context of encapsulation, all data in C can be viewed as public by default. The level of access to variables in structures ( struct
) can be changed to private if you isolate 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 the initialization of the structure in C requires the allocation and release of memory, several auxiliary functions have been added.
#include "private_var.h" #include <stdio.h> #include <stdlib.h> 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 Contact
structure was declared, but its content remained hidden for 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 content of the structure is unknown and attempts to read or change private data will cause a compilation error.
#include "private_var.h" #include <stdio.h> 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; }
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 of the data are arranged in the order of their declaration, pointers and pointer arithmetic will be suitable for achieving the goal.
Access to variables in the structure is limited. However, only variables are hidden, not the memory in which data is stored. Pointers can be viewed as a link to a memory address, and if this memory is available to the program, the data stored in this memory can be read and modified. If the pointer is assigned to a memory in which the structure stores its data - they can be read. Using the same structure definition (the same “.c” and “.h” files) and a modified “main.c” file, access restriction was overcome.
#include "private_var.h" #include <stdio.h> 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; }
Data in the structure was read and modified.
Functions, being external ( extern
) by default, are visible in the entire so-called 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, several steps must be performed:
static
) either in the source file (.c) or in the appropriate header file (.h);In this example, in the file “private_funct.c”, the static function print_numbers()
was defined. By the way, the function delete_contact()
successfully calls print_numbers()
because they are in the same file.
#include "private_funct.h" #include <stdio.h> #include <stdlib.h> 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); }
In the corresponding header file "private_funct.h", print_numbers()
was declared as a static function.
#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 */
The main program, “main.c”, successfully calls print_numbers()
indirectly via delete_contact()
, since both functions are in the same document. However, trying to call print_numbers()
from the main program will cause an error.
#include "private_funct.h" #include <stdio.h> 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()
call the print_numbers()
function from the main program. To do this, you can use the goto
keyword or pass a pointer to a private function to main
. 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 the encapsulation but cancel it, they are beyond the scope of this article.
Encapsulation exists outside of OOP languages. Modern OOP languages ​​make encapsulation convenient and natural. There are many ways to get around encapsulation and avoiding questionable practices will help preserve it both in C and in C ++.
Source: https://habr.com/ru/post/444602/
All Articles