Validation in Setters
Part of the Object Oriented Programming section of Coddy's C journey — lesson 20 of 61.
In the previous lesson, we created basic setters that simply assign values. But this misses a key benefit of encapsulation: the ability to protect your data by adding validation logic.
Consider a Counter that should never go negative. Without encapsulation, anyone could write c->value = -50 and corrupt the state. With a setter, you control what values are allowed:
// counter.c
void counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return; // Reject invalid input silently
}
c->value = new_value;
}Now negative values are simply ignored.
You might also choose to return a status code to indicate success or failure:
int counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return 0; // Failure
}
c->value = new_value;
return 1; // Success
}This pattern lets the caller know whether the operation succeeded. The key insight is that setters aren't just about assignment—they're gatekeepers that enforce rules about what your object's state can be. This is the real power of encapsulation: your object maintains its own integrity, regardless of what external code tries to do.
Challenge
EasyLet's build a Temperature module that protects its data using validation in the setter. You'll create a temperature tracker that only accepts values within a valid range—demonstrating how setters act as gatekeepers for your object's state.
You'll create three files:
temperature.h: Your public interface using the opaque pointer pattern. Declare aTemperaturetype with a forward declaration only. Declare these functions:create_temperature— takes an integer for initial degrees and returns a Temperature pointerfree_temperature— releases the allocated memorytemp_get_degrees— getter that returns the current temperature valuetemp_set_degrees— setter that returns anint(1 for success, 0 for failure)
TEMPERATURE_H.temperature.c: Define the actualstruct Temperaturewith a hiddenint degreesmember. Implement all functions. The key is your setter—it should only accept values between -50 and 150 (inclusive). If the value is outside this range, return 0 and leave the temperature unchanged. If valid, update the value and return 1.main.c: Demonstrate the validation by attempting to set both valid and invalid temperatures, showing that invalid values are rejected while valid ones are accepted.
You will receive three inputs: the initial temperature, a valid temperature to set (within range), and an invalid temperature to attempt (outside range).
In your main file, create a Temperature with the initial value and print it. Then attempt to set the invalid temperature—since it should be rejected, print the result and show that the value remains unchanged. Finally, set the valid temperature, print the success result, and show the updated value. Free the memory and confirm cleanup.
Print the output in this format:
Initial: {degrees}
Set {invalid}: {0 or 1}
After invalid: {degrees}
Set {valid}: {0 or 1}
After valid: {degrees}
FreedFor example, with inputs 25, 100, and 200, the output would be:
Initial: 25
Set 200: 0
After invalid: 25
Set 100: 1
After valid: 100
FreedThis demonstrates the real power of encapsulation—your setter enforces the rules about what constitutes a valid temperature, protecting the object's integrity regardless of what values external code tries to assign.
Cheat sheet
Setters can include validation logic to protect data integrity. Instead of simply assigning values, they act as gatekeepers that enforce rules about valid object states.
Basic validation example - rejecting invalid input silently:
void counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return; // Reject invalid input
}
c->value = new_value;
}Returning a status code to indicate success or failure:
int counter_set_value(Counter *c, int new_value) {
if (new_value < 0) {
return 0; // Failure
}
c->value = new_value;
return 1; // Success
}This pattern allows callers to check whether the operation succeeded, enabling objects to maintain their own integrity regardless of external code attempts.
Try it yourself
#include <stdio.h>
#include "temperature.h"
int main() {
// Read inputs
int initial, valid_temp, invalid_temp;
scanf("%d", &initial);
scanf("%d", &valid_temp);
scanf("%d", &invalid_temp);
// TODO: Create a Temperature with the initial value
// TODO: Print initial temperature
// Format: "Initial: {degrees}"
// TODO: Attempt to set the invalid temperature
// Print the result and show value remains unchanged
// Format: "Set {invalid}: {0 or 1}"
// Format: "After invalid: {degrees}"
// TODO: Set the valid temperature
// Print the success result and show updated value
// Format: "Set {valid}: {0 or 1}"
// Format: "After valid: {degrees}"
// TODO: Free the memory and print "Freed"
return 0;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Modular Programming Basics
Header FilesInclude GuardsSource FilesStatic FunctionsRecap: Modular Calculator4Encapsulation
Opaque Pointers ConceptDefining Opaque StructsGetters and SettersValidation in SettersRecap: Secret Box2Objects and Methods
Structs as ObjectsThe 'Self' PointerConst CorrectnessPointer vs ValueHelper MethodsRecap: Point Manager5Project: Simple Bank Account
Project SetupImplementation of Account3Object Lifecycle
Constructor PatternDestructor PatternStack InitializationDeep CopyRecap: String Wrapper6Inheritance via Composition
Struct EmbeddingThe First Member RuleAccessing Parent MembersUpcastingRecap: Shape Hierarchy