Good C++ interface has no fields
When parent- and child- classes have different memory-layouts (contain different data or its representation) and (somehow) assignment is involved, one might get in trouble by slicing part of data from the implementation on top of the other which leads to unexpected results. But, when the a class doesn't have any fields, the default (compiler-generated) assignment operator/constructor doesn't do anything, since there is nothing to assign.
Here's an example of unexpected behavior.
class PaymentService {
public:
virtual ~PaymentService() = default;
explicit PaymentService(
std::string ip, int port): ip_(ip), port_(port) {}
virtual void DoSomething() const = 0;
std::string ip_;
int port_;
};The PaymentService has 2 fields: ip_ and port, hence the constructor and assignment operation of the class do actual work of copying the data from one instance to another. The same for move-semantics. Here's an example of an implementation:
class RelayPaymentService: public PaymentService {
public:
explicit RelayPaymentService(
std::string ip, int port, std::string relay
): PaymentService(ip, port), relay_(relay) {}
void DoSomething() const override {
// some code here...
}
std::string relay_;
};
and second
class ProxyPaymentService: public PaymentService {
public:
explicit ProxyPaymentService(
std::string ip, int port, std::string proxy
): PaymentService(ip, port), proxy_(proxy) {}
void DoSomething() const override {
// some code here...
}
std::string proxy_;
};The implementation has an extra-field relay_. Notice, all fields are public for educational purpose and shouldn't be exposed by default. Now, let's define a possible use-case where an instance might be sliced, but it is hard to notice:
void assignPaymentService(PaymentService& one, PaymentService& two) {
one = two;
}Simply assigning an reference of one instance to another cause here some troubles. Here's an example of code running the above-mentioned code:
std::cout << "Running example1...\n";
std::unique_ptr<RelayPaymentService> relay_payment_service =
std::make_unique<RelayPaymentService>(
"127.0.0.1",
8080,
"<relay>"
);
std::unique_ptr<ProxyPaymentService> proxy_payment_service =
std::make_unique<ProxyPaymentService>(
"192.168.1.12",
8081,
"<proxy>"
);
assignPaymentService(*relay_payment_service, *proxy_payment_service);
std::cout << "ip: " << relay_payment_service->ip_ << ",\n"
<< "port: " << relay_payment_service->port_ << ",\n"
<< "relay: " << relay_payment_service->relay_ << ",\n";Now we have multiple possible outcomes:
The code doesn't compile since we try to assign 2 different types
ip_andport_are not changed since we try to assign 2 different typesrelay_value would turn toproxy_value which is"<proxy>".
None of the things, actually, happen:
The code compiles successfully
The values of
ip_andport_are updatedrelay_stays"<relay>"andproxy_stays"<proxy>".
The outcome is not expected, the types of relay_payment_service and proxy_payment_service stay the same. Where in the most reference-based languages the underlying reference would be changed. In order to prevent such partial data exchange, it is good to avoid any fields in classes which are supposed to be used as interfaces.


