Recent from talks
Contribute something
Nothing was collected or created yet.
Opaque pointer
View on WikipediaIn computer programming, an opaque pointer is a special case of an opaque data type, a data type declared to be a pointer to a record or data structure of some unspecified type.
Opaque pointers are present in several programming languages including Ada, C, C++, D and Modula-2.
Use
[edit]If the language the pointer is implemented with is strongly typed, programs and procedures that have no other information about an opaque pointer type T can still declare variables, arrays, and record fields of type T, assign values of that type, and compare those values for equality. However, they will not be able to de-reference such a pointer, and can only change the object's content by calling some procedure that has the missing information.
Opaque pointers are a way to hide the implementation details of an interface from ordinary clients, so that the implementation may be changed without the need to recompile the modules using it. This benefits the programmer as well since a simple interface can be created, and most details can be hidden in another file.[1] This is important for providing binary code compatibility through different versions of a shared library, for example.
This technique is described in Design Patterns as the Bridge pattern. It is sometimes referred to as "handle classes",[2] the "Pimpl idiom" (for "pointer to implementation idiom"),[3] "Compiler firewall idiom",[4] "d-pointer" or "Cheshire Cat", especially among the C++ community.[2] It is heavily used in the Qt[5] and KDE[6] libraries.
Examples
[edit]Ada
[edit]package Library_Interface is
type Handle is limited private;
-- Operations...
private
type Hidden_Implementation; -- Defined in the package body
type Handle is access Hidden_Implementation;
end Library_Interface;
The type Handle is an opaque pointer to the real implementation, that is not defined in the specification. Note that the type is not only private (to forbid the clients from accessing the type directly, and only through the operations), but also limited (to avoid the copy of the data structure, and thus preventing dangling references).
package body Library_Interface is
type Hidden_Implementation is record
... -- The actual implementation can be anything
end record;
-- Definition of the operations...
end Library_Interface;
These types are sometimes called "Taft types" – named after Tucker Taft, the main designer of Ada 95 – because they were introduced in the so-called 'Taft Amendment' to Ada 83.[7]
C
[edit]In Integer.h:
#pragma once
typedef struct Integer Integer;
/*
* The compiler considers struct obj an incomplete type. Incomplete types
* can be used in declarations.
*/
size_t integerSize(void);
void integerSetValue(Integer*, int);
int integerGetValue(Integer*);
In Integer.c:
#include "Integer.h"
typedef struct Integer {
int value;
} Integer;
/*
* The caller will handle allocation.
* Provide the required information only
*/
size_t integerSize(void) {
return sizeof(Integer);
}
void integerSetValue(Integer* i, int val) {
i->value = val;
}
int integerGetValue(Integer* i) {
return i->value;
}
This example demonstrates a way to achieve the information hiding (encapsulation) aspect of object-oriented programming using the C language. If someone wanted to change the definition of struct Integer, it would be unnecessary to recompile any other modules in the program that use the Integer.h header file unless the API was also changed. Note that it may be desirable for the functions to check that the passed pointer is not NULL, but such checks have been omitted above for brevity.
C++
[edit]In MyClass.cppm:
export module org.example.MyClass;
import std;
using std::unique_ptr;
export namespace org::example {
class MyClass {
private:
struct IntPair; // Not defined here
unique_ptr<IntPair> ptr; // Opaque pointer
public:
MyClass(); // Constructor
MyClass(const MyClass&); // Copy constructor
MyClass(MyClass&&); // Move constructor
MyClass& operator=(const MyClass&); // Copy assignment operator
MyClass& operator=(MyClass&&); // Move assignment operator
~MyClass(); // Destructor
// Other operations...
};
}
In MyClass.cpp:
module org.example.MyClass;
namespace org::example {
struct MyClass::IntPair {
int a;
int b;
};
MyClass::MyClass():
ptr{std::make_unique<IntPair>()} {}
MyClass::MyClass(const MyClass& other):
ptr{std::make_unique<MyClass>(*other.ptr)} {}
MyClass::MyClass(MyClass&& other) = default;
MyClass& MyClass::operator=(const MyClass& other) {
*ptr = *other.ptr;
return *this;
}
MyClass& MyClass::operator=(MyClass&&) = default;
MyClass::~MyClass() = default;
}
See also
[edit]References
[edit]- ^ Chris McKillop. "Programming Tools — Opaque Pointers". QNX Software Systems. Archived from the original on 2021-11-12. Retrieved 2019-01-16.
- ^ a b Eckel, Bruce (2000). "Chapter 5: Hiding the implementation". Thinking in C++. Vol. 1: Introduction to standard C++ (2nd ed.). Prentice Hall. ISBN 0-13-979809-9.
- ^ Batov, Vladimir (2008-01-25). "Making Pimpl Easy". Dr. Dobb's Journal. Retrieved 2008-05-07.
- ^
Sutter, Herb (2009) [July-August 1998]. "The Joy of Pimpls (or, more about the compiler-firewall idiom)". C++ Report. Vol. 10, no. 7. Retrieved 16 September 2025 – via gotw.ca.
[linked to] original article substantially as first published; most current vers. in book Exceptional C++ (2000, Addison-Wesley)
{{cite magazine}}: CS1 maint: url-status (link) - ^ "d-Pointer". Qt wiki. Retrieved 23 Dec 2016.
- ^ "Policies/Binary Compatibility Issues With C++ — Using a d-Pointer". KDE Community Wiki. Retrieved 2025-12-16.
- ^ Robert A. Duff (29 July 2002). "Re: What's its name again?". Newsgroup: comp.lang.ada. Archived from the original on 29 July 2009. Retrieved 11 October 2007.
External links
[edit]- "The Pimpl idiom".
- "Compilation Firewalls". or Sutter, Herb. "Compilation Firewalls".
- "The fast Pimpl idiom".
Opaque pointer
View on GrokipediaFundamentals
Definition
An opaque pointer is a pointer to a data structure or type whose internal representation and layout are deliberately hidden from client code, typically implemented as a pointer to an incomplete type such as a forward-declared structure without its full definition.[4][5] This approach ensures that the pointer serves as a handle to the underlying object while preventing direct access to its contents, thereby enforcing controlled interaction through predefined interfaces.[4] Key characteristics of an opaque pointer include its visibility and usability for operations like passing arguments to functions or returning values from them, without allowing dereferencing or inspection of the pointed-to data without module-provided functions.[6] The pointer type is fully specified in the interface, but the pointee remains incomplete, meaning the compiler knows the pointer's size and can perform type checking on pointer operations, yet lacks knowledge of the internal fields to block unauthorized access.[4] This design promotes information hiding by separating the public interface from the private implementation details.[5] An opaque pointer represents a special case of an opaque type specifically applied to pointers, where the opacity arises from declaring the target type as incomplete via forward declarations, such as naming a structure without defining its members in the client-facing header.[4] This distinguishes it from fully opaque data types, which may not involve pointers, and from transparent pointers to complete types that permit direct field access; instead, forward declarations enable modular compilation while concealing the type's layout to maintain abstraction.[5]Purpose and Motivation
Opaque pointers primarily serve to implement information hiding, a foundational principle in software design that separates the public interface of a module from its internal implementation details. This approach allows developers to conceal the specifics of data structures and algorithms, enabling changes to the underlying representation without affecting dependent code, thereby enhancing system flexibility, comprehensibility, and maintainability. Originating from David Parnas's seminal work on modular decomposition, information hiding emphasizes protecting modules from unnecessary exposure to design decisions likely to evolve, which directly motivates the use of opaque pointers to enforce such boundaries in procedural languages.[7] Another critical motivation is achieving binary compatibility across library versions, allowing updates to internal data layouts without necessitating recompilation of client applications. For instance, when a library modifies the size or members of a structure, an opaque pointer to an incomplete type remains unchanged in the public header, preserving the application binary interface (ABI) and reducing deployment friction in large-scale software ecosystems. This capability is particularly valuable in shared library environments where forward and backward compatibility must be maintained over time.[8][9] In practice, opaque pointers find common application in API design for libraries, where they hide platform-specific details such as operating system dependencies or hardware abstractions, ensuring portable and stable interfaces for users. They also support modular programming by preventing direct manipulation of internal state, which safeguards encapsulation and minimizes the propagation of errors or unintended dependencies across components. As building on the concept of an opaque pointer as a reference to an undisclosed data structure, these uses promote robust, evolvable software architectures.[9] Historically, the adoption of opaque pointers emerged from the structured programming paradigms of the 1970s and 1980s, which sought to foster maintainable and versioned software in emerging systems languages like C. This evolution aligned with the growing emphasis on abstraction to manage complexity in increasingly large programs, culminating in the ANSI C standard's formal recognition of incomplete types in 1989, which provided a standardized mechanism to support such idioms without prior ad-hoc workarounds.[7][10]Implementation
Mechanisms in Low-Level Languages
In low-level languages like C, opaque pointers are primarily implemented using incomplete struct declarations, which define a structure type without specifying its members or size. This approach, known as a forward declaration (e.g.,struct opaque_struct;), creates an incomplete type that allows pointers to the struct to be declared and used for passing data, but prohibits operations that require knowledge of the underlying layout, such as computing the size with sizeof or accessing members directly.[11] The C standard explicitly restricts such operations on incomplete types to prevent clients from depending on internal details, ensuring the pointer remains truly opaque.[11] This mechanism forms the foundation for encapsulation in procedural code, where the full struct definition is confined to the implementation module.[12]
Allocation and management of opaque pointers follow a lifecycle controlled by the implementer, typically through factory functions that abstract memory operations from the client. A creation function, such as one that allocates and initializes the underlying struct (often via malloc or similar), returns the opaque pointer to the client, while a corresponding destruction function handles deallocation and cleanup.[12] These pointers are passed by value in function calls, effectively behaving as references to the hidden data, allowing manipulation without exposing the struct's contents.[12] This pattern ensures that clients interact solely with the interface, avoiding direct memory management that could lead to errors or platform dependencies.[12]
Error handling in opaque pointer mechanisms relies on indirect indicators, as direct inspection of the pointer's target is impossible due to its incompleteness. Creation functions commonly return a null pointer to signal allocation failure, prompting clients to perform null checks before use, while other operations may return integer status codes (e.g., 0 for success, negative values for errors) or set global error indicators like errno.[11] This design enforces defensive programming, where clients must validate handles explicitly, reducing risks from invalid states without revealing implementation specifics.[12]
The opacity provided by incomplete types enhances portability by abstracting the memory layout of the pointed-to data, allowing implementers to modify struct internals—such as adding fields or adjusting alignments—without requiring client recompilation or source changes.[12] This separation supports cross-platform development, as the interface remains stable across varying architectures, compilers, or operating systems, minimizing binary compatibility issues.[12]
Handling in Object-Oriented Contexts
In object-oriented programming, opaque pointers are adapted through techniques like the pointer-to-implementation (pImpl) idiom, which employs a forward-declared class to conceal private members and implementation details within a separate structure. This approach enhances encapsulation by limiting header files to public interfaces, thereby reducing compilation dependencies and allowing internal changes without recompiling client code. Abstract base classes further integrate opaque pointers by defining pure virtual interfaces that clients interact with via handles to incomplete types, hiding the concrete implementation hierarchies.[13] Opaque pointers facilitate polymorphism by serving as handles to derived types, where virtual function calls are dispatched without exposing the underlying inheritance structure to clients. This enables runtime polymorphism through abstract interfaces, as the opaque handle forwards invocations to the hidden implementation, preserving type safety and extensibility while abstracting away class derivations. Modern object-oriented languages leverage smart pointers, such as those managing ownership of incomplete types, to handle memory for opaque pointers automatically. For instance, unique ownership semantics ensure resource acquisition is initialization (RAII) compliance without revealing internal allocations, as the complete type is only defined in the implementation unit. This mitigates manual memory management issues inherent in procedural contexts.[14] Challenges arise when integrating opaque pointers polymorphically, particularly in avoiding object slicing—where a derived object is implicitly truncated to a base—by always using pointer or reference semantics for handles, and preventing undefined behavior from incomplete types during destruction or reset operations. Without a custom deleter or virtual destructor in base classes, deallocating through an opaque base pointer to a derived incomplete type can invoke incorrect cleanup, leading to leaks or crashes.[14]Language Examples
C Usage
In C, opaque pointers are typically declared using a forward declaration of a structure followed by a typedef to its pointer type, ensuring the internal structure remains hidden from client code. The common pattern is to definetypedef struct _Handle *Handle; in a header file, where _Handle is an incomplete type whose full definition is provided only in the corresponding implementation file. This approach leverages C's allowance for pointers to incomplete types, preventing direct access to the structure's members and enforcing the use of library-provided functions for manipulation.[12]
A representative API using opaque pointers might include functions for creation, destruction, and operations on the handle, all passing the pointer by value without allowing dereference. For example:
// In header file (e.g., example.h)
typedef struct _Handle *Handle;
Handle create(int size);
void destroy(Handle h);
int get_value(Handle h);
// In header file (e.g., example.h)
typedef struct _Handle *Handle;
Handle create(int size);
void destroy(Handle h);
int get_value(Handle h);
// In source file (e.g., example.c)
struct _Handle {
int value;
// Other private members
};
Handle create(int size) {
Handle h = malloc(sizeof(struct _Handle));
if (h) {
h->value = 0; // Initialization
}
return h;
}
void destroy(Handle h) {
free(h);
}
int get_value(Handle h) {
return h ? h->value : -1; // Safe access within library
}
// In source file (e.g., example.c)
struct _Handle {
int value;
// Other private members
};
Handle create(int size) {
Handle h = malloc(sizeof(struct _Handle));
if (h) {
h->value = 0; // Initialization
}
return h;
}
void destroy(Handle h) {
free(h);
}
int get_value(Handle h) {
return h ? h->value : -1; // Safe access within library
}
Handle my_handle = create(10); int val = get_value(my_handle); destroy(my_handle); without knowledge of the internal layout.[12][9]
Header files expose only the typedef and function prototypes, while the complete structure definition is confined to the implementation's source file or a private header included solely within that module. Clients including the public header cannot instantiate the structure on the stack, allocate it directly, or access its fields, as the compiler lacks the size or member information; instead, they must rely on the library's allocation and accessor functions. This separation of interface from implementation supports modular compilation, where the library is built into an object file or library, and clients link against it without recompiling the internals.[12][15]
This pattern became prevalent in C libraries from the 1970s and 1980s, such as the standard I/O library's FILE type in stdio.h, due to C's lack of built-in support for data encapsulation and the need to abstract complex implementations in early UNIX systems. Examples include opaque handles in file operations and process management APIs, where direct structure access was avoided to maintain portability and hide platform-specific details.[12]
C++ Usage
In C++, opaque pointers are commonly employed through the Pointer to Implementation (Pimpl) idiom, which encapsulates the private details of a class behind a forward-declared incomplete type, thereby reducing compilation dependencies and enhancing binary compatibility. In this pattern, the public class header declares a nested struct as incomplete, such asstruct Impl;, and holds a private smart pointer to it, for example:
class Widget {
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
public:
Widget();
~Widget();
// Public interface methods forward to pImpl
};
class Widget {
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
public:
Widget();
~Widget();
// Public interface methods forward to pImpl
};
Impl fully and provides the necessary member functions, ensuring clients cannot access or manipulate the internal structure directly.[16] This approach leverages incomplete types to enforce opacity at compile time: operations like sizeof(Impl) or member access trigger errors because the type lacks a complete definition in the client context.[17] Furthermore, if a client attempts to implement or inline functions involving the incomplete type, the linker will fail during resolution, as the symbol details are hidden in the separate translation unit.[13]
Opaque pointers integrate seamlessly with C++ templates to create generic APIs that maintain encapsulation, such as a templated handle class wrapping an incomplete type pointer. For instance:
template <typename T>
class OpaqueHandle {
private:
T* [handle](/page/Handle); // T is incomplete in client code
public:
OpaqueHandle();
~OpaqueHandle();
// Template methods forward operations without exposing T's internals
};
template <typename T>
class OpaqueHandle {
private:
T* [handle](/page/Handle); // T is incomplete in client code
public:
OpaqueHandle();
~OpaqueHandle();
// Template methods forward operations without exposing T's internals
};
T remains incomplete to clients, preventing direct manipulation while allowing type-safe forwarding in library implementations; this is particularly useful in reusable components like graphics or networking APIs.[18] The compiler enforces this by disallowing instantiation or dereference of T without its full definition, thus preserving abstraction in generic code.[17]
The usage of opaque pointers in C++ has evolved from raw pointers in early standards, which required manual memory management and risked leaks, to reliance on smart pointers like std::unique_ptr introduced in C++11 for automatic resource acquisition and exception safety.[16] This shift aligns with RAII principles, ensuring deterministic cleanup of the opaque implementation even in the presence of exceptions, while templates enable more flexible, type-parameterized opaque handles without compromising safety.
Ada Usage
In Ada, opaque pointers are implemented through private types and limited private types within packages, which enforce abstraction by hiding the full type definition from clients while providing a partial view as an opaque handle. This approach allows developers to create type-safe interfaces for data structures without exposing internal representations, promoting modularity and maintainability in large-scale systems.[19] The mechanism originates from Ada 83, where private types were introduced to support data abstraction in embedded and safety-critical applications, enabling information hiding to prevent unintended direct access to implementation details.[20] In a package specification, a private type is declared without its full structure, serving as an opaque handle; for instance, access types can be used to point to hidden objects. The following example illustrates a package for a stack whereHandle acts as an opaque pointer:
package Stack is
type Handle is private;
procedure Push (S : in out Handle; Value : in [Integer](/page/Integer));
procedure Pop (S : in out Handle; Value : out [Integer](/page/Integer));
function Is_Empty (S : Handle) return [Boolean](/page/Boolean);
private
type Stack_Type;
type Handle is access Stack_Type;
end Stack;
package Stack is
type Handle is private;
procedure Push (S : in out Handle; Value : in [Integer](/page/Integer));
procedure Pop (S : in out Handle; Value : out [Integer](/page/Integer));
function Is_Empty (S : Handle) return [Boolean](/page/Boolean);
private
type Stack_Type;
type Handle is access Stack_Type;
end Stack;
Handle type through exported operations like Push and Pop, without knowledge of the underlying Stack_Type structure.[21]
The full view of the private type is deferred to the package body, where the implementation is revealed only to the package's internal procedures. For example:
package body Stack is
type Stack_Type is record
Top : [Integer](/page/Integer) := 0;
Content : [array](/page/Array) (1 .. 100) of [Integer](/page/Integer);
end record;
-- Implementations of [Push, Pop](/page/Push_Pop), etc., using the full view
procedure Push (S : in out [Handle](/page/Handle); Value : in [Integer](/page/Integer)) is
begin
if S.Top < 100 then
S.Top := S.Top + 1;
S.Content(S.Top) := Value;
end if;
end Push;
-- Similar for other operations
end Stack;
package body Stack is
type Stack_Type is record
Top : [Integer](/page/Integer) := 0;
Content : [array](/page/Array) (1 .. 100) of [Integer](/page/Integer);
end record;
-- Implementations of [Push, Pop](/page/Push_Pop), etc., using the full view
procedure Push (S : in out [Handle](/page/Handle); Value : in [Integer](/page/Integer)) is
begin
if S.Top < 100 then
S.Top := S.Top + 1;
S.Content(S.Top) := Value;
end if;
end Push;
-- Similar for other operations
end Stack;
type Handle is limited private; in the specification disables default copying, compelling use of explicit procedures for handle management and integrating runtime checks with the language's type safety guarantees.[21][19]
