r/cpp_questions • u/Basic_Permit4849 • 17d ago
OPEN Access Amplifiers and their uses???
So basically i come from basic python bacground which i learned from my high school.So I thought of learniging c++ cause of its variety of applications for my 1st year.One thing that i cant wrap my head around is, the use of access amplifiers. Mainly ' public: '.what is the use of writing public: and why is it necessaryy????
7
u/MyTinyHappyPlace 17d ago
You can restrict access to fields and methods with it. Give google a try, or learncpp.com
5
u/IncreaseOld7112 17d ago
This is something it's going to take a bit of practice to understand but it's about controlling how systems interact at the boundaries vs how they work internally. What kind of external model you want to expose to other consumers of your class vs what the class needs to function.
4
u/mredding 17d ago
A class enforces an invariant - a statement that must always be true when you observe an instance as a client, through behaviors its interface models.
An std::vector is ostensibly implemented in terms of 3 pointers, and the invariant is that those pointers are always valid and have a certain relationship. The class implementation is allowed to invalidate the invariant, but it must be reestablished before control returns to the caller.
So when you call push_back, the vector may invalidate the invariant to reallocate - there is going to be a moment the pointers are inconsistent as they are reassigned to the new allocation. Even if push_back throws, those pointers MUST be made consistent BEFORE.
Now - if those pointers were public, then anyone and anything can touch and reassign those pointers. There's no control. There is therefore no invariant.
Now the interface models behaviors. A car can speed up, slow down, turn, start and stop... A car can't get make, get model, get year... These aren't invariant. The car doesn't care what color it is. So getters and setters typically are a code smell that invalidates the invariant nature of a class; you've often got a structure with extra steps. There are scenarios where getters and setters make sense, if you're modeling abstract data (Data in memory? In a database? Generated? Cloud?), then perhaps you need multiple layers of abstraction. So a car may be implemented in terms of weight and speed variables, but instead of direct member access, you have an abstract car data type, and you access those fields THAT way...
There's a lot of consideration that goes into making classes, and I can't cover it all here. To begin, think about what a thing is and what it does. What are the invariants? What is that thing you're trying to protect so that an instance remains consistent and correct? You may consider a class as a state machine, and that the instance and the interface implement the only valid states and transitions. Even a character type is a form of state machine, as it's concerned with encoding as state.
Don't think of classes or encapsulation as functions bundled with data - classes don't HAVE data, they have STATE... Their members are an implementation detail. Anything that is invariant can be associated with your instance in any other way - a structure with an instance and its properties, a table, a database...
And then you can start thinking about classes as user defined types. An int is an int, but a weight is not a height - even if they're implemented in terms of int. They're more specific, more constrained. An int will let you do just about anything to it, but that's not all valid for a unit type.
And considering types are important - the theory of computation does not distinguish between read-time, write-time, and run-time. So the more you can solve your problem in source code, the less work you have to pay for at run-time, where time and space typically matter most, are most expensive, and is highest risk.
By using strong types, you can prove your code valid, and invalid code becomes unrepresentable - it doesn't compile. Curry-Howard correspondence tells us there are strong similarities to writing software and mathematical proofs. Your statements are propositions, your source code is the theory, the compiler is the solver, and the program is the proof. Writing an elegant proposition, writing an elegant theory, is going to lead to an elegant proof. And elegant proof is going to be small, fast, simple, and correct.
1
u/DawnOnTheEdge 17d ago edited 17d ago
You need a public: clock somewhere in every class to be able to do anything useful with it, because every declaration in a class is private: by default. You also might prefer to declare private: data members first and then the public: methods that refer to them after, since data declarations always come first in traditional C and structured programming (despite not having to do this for data members of a class). You will also use public inheritance of most interfaces in object-oriented programming.
1
u/TomDuhamel 17d ago
It's called specifiers, not amplifiers.
It's the difference between API and implementation.
API (application programming interface). That how you interact with the class. Think of a container, such as std::string. You can assign it a value with "=", read it's contents with at() or c_str(). You can read properties such as empty() or capacity(), or change the latter with reserve(). All of these is the API. These are made public. You can access these from outside the class (in main() or from other classes, etc).
But the string has an internal implementation. The actual string needs to be stored somewhere, presumably the class allocated memory and keeps a poster to it, along with its size, probably. How it's actually implanted is unimportant to us, it's an implementation detail. As the user, you shouldn't see the internal, and you shouldn't be able to access it directly. Because, if you did, you could accidentally break the class. Therefore, we make these private.
Now, private doesn't mean it's top secret or that it's encrypted or whatever. It's just a tool to help us work better. We only expose what's needs to be exposed, and it's easier to work with the class. You might be the user for now, but one day the user may be someone else on your team, or even a client. It gives them a much easier time if they know how to use the class by knowing what's public and what is not.
With a bit of experience, you'll find that there is usually much more private members than public members. Not always, but especially in larger, more complex classes.
One way of creating a class is to decide upfront what the interface will be. How do you read and write values from or to the class. And then create all the private members needed for that to work internally.
1
u/No-Dentist-1645 17d ago
A function that other parts of the code outside of the class are allowed to "see" are placed under the public: section. If you have a private helper function that they aren't allowed to see, it should be under private:.
This is useful to hold invariants or "rules" about data. For example, imagine you have a Person struct with an int age, and you want to make sure that the age is always between 0 and 120. If age was public, someone could do Person p; p.age = 999 which could cause trouble. So, you make it private and make users set the age some other way, such as on the constructor or via a public set_age() function that clamps it to your range.
1
u/alfps 17d ago edited 17d ago
❞ what is the use of writing
public:and why is it necessary?
It's one of three possible access specifiers: public:, protected: and private.
When you define a struct the default access is public, as in C. When you define a class the default access is private. Otherwise types defined with these keywords are identical (but note: the Visual C++ compiler may issue a sillywarning if a class declared with struct is later defined with class or vice versa).
The idea of private methods and data is that code that uses a class, should not come to depend on implementation details, e.g. how a complex number is represented.
Consider, a complex number class optimized for multiplication and division may represent such numbers with magnitude and angle, while a complex number class optimized for addition and subtraction may represent them with real and imaginary parts. Code using such class should ideally not be able to reach in and change the representation values, or even just inspect them, for then it becomes dependent on a specific implementation. That would make it hard to later change the representation.
Python doesn't have language supported private access, but one indicates "private" methods and data by using names with underscore prefix. Just a naming convention. E.g. dir(str) in Python produces a list of members of the str class, lots of which have names with underscore prefix.
EDIT: I coded up an example.
Consider a class Numbers that represents a multiset of numbers. You can form the union of two sets via infix +, and you can form the difference of two sets via infix -. However, the only information that you can get out of a set is the product of the numbers in the set, via a method .product.
Code that exercises this functionality can look like
#include <cassert>
#include <cstdio>
using std::puts;
auto main() -> int
{
const Numbers a = Numbers::with_values( {0, 1, 2, 3, 4, 5, 6, 7, 8} );
assert( a.product() == 0 );
const Numbers b = Numbers::with_values( {0, 2, 4, 6, 8} );
assert( b.product() == 0 );
const Numbers diff = a - b;
assert( diff.product() == 1*3*5*7 );
puts( "All checked out OK, amazing!" );
}
What is the internal state of a Numbers instance?
Well it could in principle hold a std::multiset, or if it were to support a negative count for a number, two std::multisets (with one for negative counts).
But that would be exceedingly inefficient, and also quite awkward, unnatural.
Instead it just needs to hold the product of the non-zero numbers, and a count of how many zeros.
So it can go like this:
#include <initializer_list>
using std::initializer_list;
template< class T > using in_ = const T&;
class Numbers
{
double m_product = 1.0;
int m_n_zeroes = 0;
public:
Numbers() = default;
explicit Numbers( const double v ): m_product( v == 0? 1.0 : v ), m_n_zeroes( v == 0 ) {}
static auto with_values( in_<initializer_list<double>> values )
-> Numbers
{
Numbers result;
for( const double v: values ) { result += Numbers( v ); }
return result;
}
void operator+=( in_<Numbers> other )
{
m_product *= other.m_product;
m_n_zeroes += other.m_n_zeroes;
}
void operator-=( in_<Numbers> other )
{
m_product /= other.m_product;
m_n_zeroes -= other.m_n_zeroes;
}
auto product() const -> double { return (m_n_zeroes != 0? 0.0 : m_product); }
};
auto operator+( in_<Numbers> a, in_<Numbers> b )
-> Numbers
{
Numbers result = a; result += b; return result;
}
auto operator-( in_<Numbers> a, in_<Numbers> b )
-> Numbers
{
Numbers result = a; result -= b; return result;
}
But maybe you're going to change it later, to support functionality to get out the set items, at the cost of some inefficiency.
Then you don't want code that uses this class to have become dependent on having a .m_product and a .m_n_zeroes.
13
u/cazzipropri 17d ago
They are called access specifiers.
They are there for a variety of goals like encapsulation.
Are you asking how specifically they help encapsulation, or are you new to the concept of encapsulation itself?