Agent Skills: Convert Java to C++

Convert Java code to idiomatic C++. Use when migrating Java projects to C++, translating Java patterns to idiomatic C++, or refactoring Java codebases. Extends meta-convert-dev with Java-to-C++ specific patterns.

UncategorizedID: arustydev/ai/convert-java-cpp

Repository

aRustyDevLicense: AGPL-3.0
72

Install this agent skill to your local

pnpm dlx add-skill https://github.com/aRustyDev/agents/tree/HEAD/content/skills/convert-java-cpp

Skill Files

Browse the full folder contents for convert-java-cpp.

Download Skill

Loading file tree…

content/skills/convert-java-cpp/SKILL.md

Skill Metadata

Name
convert-java-cpp
Description
Convert Java code to idiomatic C++. Use when migrating Java projects to C++, translating Java patterns to idiomatic C++, or refactoring Java codebases. Extends meta-convert-dev with Java-to-C++ specific patterns.

Convert Java to C++

Convert Java code to idiomatic C++. This skill extends meta-convert-dev with Java-to-C++ specific type mappings, idiom translations, and tooling.

This Skill Extends

  • meta-convert-dev - Foundational conversion patterns (APTV workflow, testing strategies)

For general concepts like the Analyze → Plan → Transform → Validate workflow, testing strategies, and common pitfalls, see the meta-skill first.

This Skill Adds

  • Type mappings: Java types → C++ types
  • Idiom translations: Java patterns → idiomatic C++
  • Error handling: Java exceptions → C++ exception handling and RAII
  • Memory management: Java GC → C++ manual memory management and smart pointers
  • Concurrency: Java threads → C++ threading and async patterns
  • Build systems: Maven/Gradle → CMake/Make

This Skill Does NOT Cover

  • General conversion methodology - see meta-convert-dev
  • Java language fundamentals - see lang-java-dev
  • C++ language fundamentals - see lang-cpp-dev
  • Reverse conversion (C++ → Java) - see convert-cpp-java

Quick Reference

| Java | C++ | Notes | |------|-----|-------| | String | std::string | Owned string | | int | int / int32_t | 32-bit signed integer | | long | long / int64_t | 64-bit signed integer | | double | double | 64-bit float | | boolean | bool | Boolean type | | Object | void* / templates | Avoid void*, use templates | | List<T> | std::vector<T> | Dynamic array | | Set<T> | std::set<T> / std::unordered_set<T> | Ordered/unordered set | | Map<K, V> | std::map<K, V> / std::unordered_map<K, V> | Ordered/unordered map | | Optional<T> | std::optional<T> | Nullable value (C++17) | | Stream<T> | Range views (C++20) / iterators | Lazy evaluation | | interface | class (abstract) | Pure virtual functions | | class | class / struct | Similar but different memory model | | package | namespace | Code organization | | synchronized | std::mutex + std::lock_guard | Thread safety |

When Converting Code

  1. Analyze source thoroughly before writing target
  2. Map types first - create type equivalence table
  3. Preserve semantics over syntax similarity
  4. Adopt C++ idioms - don't write "Java code in C++ syntax"
  5. Handle memory explicitly - GC → RAII and smart pointers
  6. Test equivalence - same inputs → same outputs

Type System Mapping

Primitive Types

| Java | C++ | Notes | |------|-----|-------| | byte | int8_t / char | 8-bit signed | | short | int16_t / short | 16-bit signed | | int | int / int32_t | 32-bit signed | | long | long long / int64_t | 64-bit signed | | float | float | 32-bit floating point | | double | double | 64-bit floating point | | char | char16_t / wchar_t | 16-bit Unicode in Java, varies in C++ | | boolean | bool | Boolean type | | void | void | No return value |

Collection Types

| Java | C++ | Notes | |------|-----|-------| | ArrayList<T> | std::vector<T> | Dynamic array, contiguous memory | | LinkedList<T> | std::list<T> | Doubly-linked list | | HashSet<T> | std::unordered_set<T> | Hash-based set, O(1) lookup | | TreeSet<T> | std::set<T> | Sorted set, O(log n) lookup | | HashMap<K, V> | std::unordered_map<K, V> | Hash-based map, O(1) lookup | | TreeMap<K, V> | std::map<K, V> | Sorted map, O(log n) lookup | | Queue<T> | std::queue<T> | FIFO queue | | Stack<T> | std::stack<T> | LIFO stack | | Deque<T> | std::deque<T> | Double-ended queue | | T[] | std::array<T, N> / std::vector<T> | Fixed/dynamic array |

Composite Types

| Java | C++ | Notes | |------|-----|-------| | class | class | Classes with constructors/destructors | | interface | class with pure virtuals | Abstract base class | | abstract class | class with virtuals | Base class with implementation | | enum | enum class | Strongly-typed enum (C++11) | | @interface (annotation) | Attributes (C++11) | Limited compared to Java | | record (Java 14+) | struct | Immutable data class | | sealed class (Java 17+) | final class | Restrict inheritance |

Generic Type Mappings

| Java | C++ | Notes | |------|-----|-------| | <T> | template<typename T> | Generic type parameter | | <T extends Base> | template<typename T> + static_assert | Type constraints | | <T extends A & B> | Concepts (C++20) | Multiple constraints | | <? extends T> | const T& / T | Upper bound wildcard | | <? super T> | - | No direct equivalent | | List<?> | Templates + type erasure | Complex pattern |

Special Mappings

| Java | C++ | Notes | |------|-----|-------| | Object | std::any (C++17) / templates | Prefer templates | | null | nullptr | Null pointer | | Optional<T> | std::optional<T> (C++17) | Nullable value | | String | std::string | UTF-8 string | | StringBuilder | std::ostringstream / std::string | String building | | Comparable<T> | operator< | Comparison operator | | Iterable<T> | Iterator pattern | Range-for compatible | | Runnable | std::function<void()> / lambda | Callable object | | Callable<T> | std::function<T()> / lambda | Callable with return |


Module System Translation

Package → Namespace

Java:

package com.example.myapp;

public class User {
    private String name;
    private int age;
}

C++:

// user.hpp
#ifndef COM_EXAMPLE_MYAPP_USER_HPP
#define COM_EXAMPLE_MYAPP_USER_HPP

#include <string>

namespace com::example::myapp {

class User {
private:
    std::string name;
    int age;

public:
    User(const std::string& name, int age);
    // Getters/setters
};

}  // namespace com::example::myapp

#endif

Why this translation:

  • Java packages map to C++ namespaces (use :: separator in C++17+)
  • Header guards (#ifndef) prevent multiple inclusion
  • Declarations in .hpp, definitions in .cpp

Import → Include

Java:

import java.util.List;
import java.util.ArrayList;
import java.util.stream.*;

C++:

#include <vector>
#include <algorithm>
#include <ranges>  // C++20

Why this translation:

  • Java imports map to C++ includes
  • C++ uses header files, not package-based imports
  • Standard library in <>, local files in ""

Visibility Modifiers

| Java | C++ | Notes | |------|-----|-------| | public | public: | Accessible everywhere | | protected | protected: | Accessible in class and subclasses | | private | private: | Accessible only in class | | package-private (default) | - | No direct equivalent, use unnamed namespace |


Idiom Translation

Pattern 1: Getter/Setter → Direct Access or Property

Java:

public class User {
    private String name;
    private int age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
        this.age = age;
    }
}

C++:

class User {
private:
    std::string name;
    int age;

public:
    // Const getters
    const std::string& getName() const { return name; }
    int getAge() const { return age; }

    // Setters with validation
    void setName(const std::string& name) { this->name = name; }

    void setAge(int age) {
        if (age < 0) {
            throw std::invalid_argument("Age cannot be negative");
        }
        this->age = age;
    }
};

Why this translation:

  • C++ uses const methods for getters (promise not to modify object)
  • Return const& for large objects to avoid copying
  • Throw standard exceptions (std::invalid_argument, etc.)

Pattern 2: Null Handling → Optional/Smart Pointers

Java:

public User findUser(String id) {
    for (User user : users) {
        if (user.getId().equals(id)) {
            return user;
        }
    }
    return null;
}

// Usage
User user = findUser("123");
if (user != null) {
    System.out.println(user.getName());
}

C++:

std::optional<User> findUser(const std::string& id) {
    auto it = std::find_if(users.begin(), users.end(),
        [&id](const User& u) { return u.getId() == id; });

    if (it != users.end()) {
        return *it;
    }
    return std::nullopt;
}

// Usage
auto user = findUser("123");
if (user.has_value()) {
    std::cout << user->getName() << '\n';
}

// Or with value_or
auto name = findUser("123")
    .transform([](const User& u) { return u.getName(); })
    .value_or("Unknown");

Why this translation:

  • std::optional makes null handling explicit (C++17)
  • Safer than raw pointers for optional values
  • Supports functional-style operations (transform, value_or)

Pattern 3: Collection Operations → STL Algorithms/Ranges

Java:

List<Integer> result = items.stream()
    .filter(x -> x % 2 == 0)
    .map(x -> x * 2)
    .collect(Collectors.toList());

C++:

// Traditional STL algorithms
std::vector<int> result;
std::copy_if(items.begin(), items.end(), std::back_inserter(result),
    [](int x) { return x % 2 == 0; });
std::transform(result.begin(), result.end(), result.begin(),
    [](int x) { return x * 2; });

// Or with C++20 ranges (more similar to Java)
auto result = items
    | std::views::filter([](int x) { return x % 2 == 0; })
    | std::views::transform([](int x) { return x * 2; })
    | std::ranges::to<std::vector>();

Why this translation:

  • C++20 ranges provide lazy evaluation like Java streams
  • Traditional STL algorithms are more verbose but work in older C++
  • Ranges compose better and are more readable

Pattern 4: Try-with-Resources → RAII

Java:

try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
    // Use line
} catch (IOException e) {
    e.printStackTrace();
}
// reader is automatically closed

C++:

// RAII - Resource Acquisition Is Initialization
#include <fstream>
#include <string>

try {
    std::ifstream file("file.txt");
    if (!file.is_open()) {
        throw std::runtime_error("Failed to open file");
    }

    std::string line;
    std::getline(file, line);
    // Use line
    // file is automatically closed when it goes out of scope
} catch (const std::exception& e) {
    std::cerr << e.what() << '\n';
}

Why this translation:

  • C++ uses RAII pattern - resources cleaned up in destructors
  • No need for explicit close() - automatic when object destroyed
  • More general than Java's try-with-resources

Pattern 5: Interfaces → Abstract Classes with Pure Virtuals

Java:

public interface Drawable {
    void draw();

    default void render() {
        System.out.println("Rendering...");
        draw();
    }
}

public class Circle implements Drawable {
    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }
}

C++:

class Drawable {
public:
    // Pure virtual function (must be implemented)
    virtual void draw() = 0;

    // Virtual function with default implementation
    virtual void render() {
        std::cout << "Rendering...\n";
        draw();
    }

    // Virtual destructor (important!)
    virtual ~Drawable() = default;
};

class Circle : public Drawable {
public:
    void draw() override {
        std::cout << "Drawing circle\n";
    }
};

Why this translation:

  • Pure virtual functions (= 0) = Java abstract methods
  • Virtual functions with body = Java default methods
  • Always provide virtual destructor for polymorphic classes
  • Use override keyword for safety (C++11)

Pattern 6: Inheritance → Composition (Preferred in C++)

Java:

public class Stack<T> extends ArrayList<T> {
    public void push(T item) {
        add(item);
    }

    public T pop() {
        return remove(size() - 1);
    }
}

C++:

// Prefer composition over inheritance
template<typename T>
class Stack {
private:
    std::vector<T> data;

public:
    void push(const T& item) {
        data.push_back(item);
    }

    T pop() {
        if (data.empty()) {
            throw std::runtime_error("Stack is empty");
        }
        T item = std::move(data.back());
        data.pop_back();
        return item;
    }

    bool empty() const {
        return data.empty();
    }

    size_t size() const {
        return data.size();
    }
};

Why this translation:

  • Composition is more flexible than inheritance in C++
  • Avoids exposing base class interface
  • Better encapsulation and type safety

Error Handling

Exception Mapping

| Java Exception | C++ Exception | Notes | |---------------|---------------|-------| | Exception | std::exception | Base exception class | | RuntimeException | std::runtime_error | Runtime errors | | IllegalArgumentException | std::invalid_argument | Invalid argument | | IllegalStateException | std::logic_error | Logic error | | NullPointerException | std::bad_optional_access | Null access (or use optional) | | IndexOutOfBoundsException | std::out_of_range | Array bounds | | IOException | std::ios_base::failure | I/O error | | ArithmeticException | std::overflow_error | Arithmetic error |

Exception Handling Patterns

Java:

public int divide(int a, int b) throws ArithmeticException {
    if (b == 0) {
        throw new ArithmeticException("Division by zero");
    }
    return a / b;
}

public void process() {
    try {
        int result = divide(10, 0);
    } catch (ArithmeticException e) {
        System.err.println("Error: " + e.getMessage());
    } finally {
        cleanup();
    }
}

C++:

int divide(int a, int b) {
    if (b == 0) {
        throw std::invalid_argument("Division by zero");
    }
    return a / b;
}

void process() {
    try {
        int result = divide(10, 0);
    } catch (const std::invalid_argument& e) {
        std::cerr << "Error: " << e.what() << '\n';
    }
    // No finally - use RAII for cleanup
}

// RAII handles cleanup automatically
class Resource {
public:
    Resource() { /* acquire */ }
    ~Resource() { cleanup(); }  // Always called
};

Why this translation:

  • C++ doesn't have finally - use RAII instead
  • Catch by const& to avoid slicing
  • Throw standard exceptions or custom types
  • No checked exceptions in C++

Custom Exceptions

Java:

public class ValidationException extends Exception {
    public ValidationException(String message) {
        super(message);
    }
}

C++:

class ValidationException : public std::exception {
private:
    std::string message;

public:
    explicit ValidationException(const std::string& msg) : message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }
};

Memory Management

Java GC → C++ Manual Memory

Java (GC):

public class DataProcessor {
    private List<Data> cache = new ArrayList<>();

    public void addData(Data data) {
        cache.add(data);  // GC handles cleanup
    }
}

C++ (RAII + Smart Pointers):

class DataProcessor {
private:
    std::vector<std::shared_ptr<Data>> cache;

public:
    void addData(std::shared_ptr<Data> data) {
        cache.push_back(data);
        // Automatically cleaned up when last shared_ptr is destroyed
    }

    // Or with unique ownership
    void addDataUnique(std::unique_ptr<Data> data) {
        cache.push_back(std::move(data));
    }
};

Smart Pointer Decision Tree

Is the object optional/nullable?
├─ YES → std::optional<T> (if small) or std::unique_ptr<T> (if large/polymorphic)
└─ NO → Direct member (T) or reference (T&)

Does the object need shared ownership?
├─ YES → std::shared_ptr<T>
└─ NO → std::unique_ptr<T> or direct ownership

Is the object polymorphic (virtual functions)?
├─ YES → Must use pointers (raw, unique, or shared)
└─ NO → Can use direct value

Memory Ownership Patterns

Java (Shared References):

public class Cache {
    private Map<String, User> users = new HashMap<>();

    public User getUser(String id) {
        return users.get(id);  // Returns reference, GC handles lifetime
    }

    public void setUser(String id, User user) {
        users.put(id, user);  // Stores reference
    }
}

C++ (Explicit Ownership):

class Cache {
private:
    std::unordered_map<std::string, std::shared_ptr<User>> users;

public:
    // Return shared pointer - shared ownership
    std::shared_ptr<User> getUser(const std::string& id) {
        auto it = users.find(id);
        if (it != users.end()) {
            return it->second;
        }
        return nullptr;
    }

    // Or return optional reference - no ownership transfer
    std::optional<std::reference_wrapper<const User>>
    getUserRef(const std::string& id) const {
        auto it = users.find(id);
        if (it != users.end()) {
            return *it->second;
        }
        return std::nullopt;
    }

    void setUser(const std::string& id, std::shared_ptr<User> user) {
        users[id] = user;
    }
};

Why this translation:

  • C++ requires explicit ownership decisions
  • Shared pointers for shared ownership (reference counting)
  • References for borrowing without ownership transfer
  • Unique pointers for exclusive ownership

Concurrency Patterns

Thread Creation

Java:

Thread thread = new Thread(() -> {
    System.out.println("Running in thread");
});
thread.start();
thread.join();

C++:

#include <thread>
#include <iostream>

std::thread thread([]() {
    std::cout << "Running in thread\n";
});
thread.join();

Synchronized → Mutex

Java:

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

C++:

#include <mutex>

class Counter {
private:
    int count = 0;
    mutable std::mutex mtx;

public:
    void increment() {
        std::lock_guard<std::mutex> lock(mtx);
        count++;
    }

    int getCount() const {
        std::lock_guard<std::mutex> lock(mtx);
        return count;
    }
};

Why this translation:

  • std::lock_guard provides RAII locking (like Java synchronized)
  • Automatically unlocks when scope ends
  • Use mutable for mutex in const methods

ExecutorService → std::async/thread pool

Java:

ExecutorService executor = Executors.newFixedThreadPool(4);

Future<String> future = executor.submit(() -> {
    Thread.sleep(1000);
    return "Result";
});

String result = future.get();
executor.shutdown();

C++:

#include <future>
#include <thread>
#include <chrono>

// Simple async
std::future<std::string> future = std::async(std::launch::async, []() {
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    return std::string("Result");
});

std::string result = future.get();

// For thread pools, use external library (e.g., Boost.Asio, Thread Pool)

CompletableFuture → std::future/promise

Java:

CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    return fetchData();
});

future.thenApply(data -> parse(data))
      .thenAccept(result -> process(result))
      .exceptionally(ex -> handleError(ex));

C++:

// C++ std::future is more limited
auto future = std::async(std::launch::async, []() {
    return fetchData();
});

try {
    auto data = future.get();
    auto result = parse(data);
    process(result);
} catch (const std::exception& ex) {
    handleError(ex);
}

// For chaining, use external library (e.g., folly::Future, boost::future)

Metaprogramming

Annotations → Attributes

Java:

@Override
public String toString() {
    return "User";
}

@Deprecated
public void oldMethod() {
    // ...
}

@SuppressWarnings("unchecked")
public List getRawList() {
    // ...
}

C++:

// C++11 attributes (limited compared to Java)
[[nodiscard]] int getValue() {
    return 42;
}

[[deprecated("Use newMethod instead")]]
void oldMethod() {
    // ...
}

[[maybe_unused]] void utilityFunction() {
    // ...
}

// C++20 attributes
[[likely]]
if (condition) {
    // Likely branch
}

[[unlikely]]
else {
    // Unlikely branch
}

Why this translation:

  • C++ attributes are compiler hints, not runtime-accessible
  • Much more limited than Java annotations
  • No custom attributes like Java's annotation processing

Reflection → Runtime Type Information (Limited)

Java:

Class<?> clazz = obj.getClass();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
    System.out.println(method.getName());
}

C++:

#include <typeinfo>

// Very limited reflection
const std::type_info& ti = typeid(obj);
std::cout << "Type: " << ti.name() << '\n';

// For more reflection, use external libraries:
// - Boost.PFR (Plain Old Data reflection)
// - rttr (Run-Time Type Reflection)
// - Meta Stuff (modern reflection library)

Note: C++ has minimal runtime reflection. Most "reflection" done at compile-time with templates.

Generics → Templates

Java:

public class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}

Box<String> box = new Box<>();

C++:

template<typename T>
class Box {
private:
    T value;

public:
    void set(const T& value) {
        this->value = value;
    }

    const T& get() const {
        return value;
    }
};

Box<std::string> box;

Why this translation:

  • C++ templates are more powerful (compile-time vs runtime)
  • Templates fully instantiated at compile-time
  • No type erasure in C++ (unlike Java)

Serialization

Jackson → Third-Party Libraries

Java:

import com.fasterxml.jackson.databind.ObjectMapper;

public class User {
    @JsonProperty("user_id")
    private String id;
    private String name;

    @JsonIgnore
    private String password;
}

ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(user);
User parsed = mapper.readValue(json, User.class);

C++:

// Using nlohmann/json library
#include <nlohmann/json.hpp>

struct User {
    std::string id;
    std::string name;
    std::string password;  // Will handle separately
};

// Define serialization
void to_json(nlohmann::json& j, const User& u) {
    j = {
        {"user_id", u.id},
        {"name", u.name}
        // password omitted
    };
}

void from_json(const nlohmann::json& j, User& u) {
    j.at("user_id").get_to(u.id);
    j.at("name").get_to(u.name);
}

// Usage
nlohmann::json j = user;
std::string json_str = j.dump();

User parsed = j.get<User>();

Popular C++ JSON libraries:

  • nlohmann/json - Modern, easy to use
  • RapidJSON - Fast, SAX/DOM parsing
  • simdjson - Extremely fast parsing
  • Boost.JSON - Part of Boost

Validation

Java:

import jakarta.validation.constraints.*;

public class User {
    @NotNull
    @Size(min = 1, max = 100)
    private String name;

    @Email
    private String email;

    @Min(0)
    @Max(150)
    private int age;
}

C++:

// Manual validation or use external library
class User {
private:
    std::string name;
    std::string email;
    int age;

public:
    User(std::string name, std::string email, int age) {
        if (name.empty() || name.length() > 100) {
            throw std::invalid_argument("Name must be 1-100 characters");
        }
        if (age < 0 || age > 150) {
            throw std::invalid_argument("Age must be 0-150");
        }
        // Email validation with regex
        std::regex email_regex(R"([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})");
        if (!std::regex_match(email, email_regex)) {
            throw std::invalid_argument("Invalid email");
        }

        this->name = std::move(name);
        this->email = std::move(email);
        this->age = age;
    }
};

Build System Translation

Maven/Gradle → CMake

Maven (pom.xml):

<project>
    <groupId>com.example</groupId>
    <artifactId>myapp</artifactId>
    <version>1.0.0</version>

    <dependencies>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>32.0.0</version>
        </dependency>
    </dependencies>
</project>

CMake (CMakeLists.txt):

cmake_minimum_required(VERSION 3.20)
project(myapp VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Dependencies
find_package(Boost REQUIRED)

add_executable(myapp
    src/main.cpp
    src/utils.cpp
)

target_include_directories(myapp PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
)

target_link_libraries(myapp PRIVATE
    Boost::boost
)

# Tests
enable_testing()
add_subdirectory(tests)

Dependency Management

| Java | C++ | Notes | |------|-----|-------| | Maven Central | vcpkg / Conan / Hunter | Package managers | | pom.xml | CMakeLists.txt | Build config | | .jar files | .a / .so / .dll | Libraries | | Modules (Java 9+) | Header files | Module system |


Testing

JUnit → Google Test / Catch2

Java (JUnit):

import org.junit.jupiter.api.*;

class CalculatorTest {
    private Calculator calc;

    @BeforeEach
    void setUp() {
        calc = new Calculator();
    }

    @Test
    void shouldAddNumbers() {
        assertEquals(5, calc.add(2, 3));
    }

    @Test
    void shouldThrowOnDivideByZero() {
        assertThrows(ArithmeticException.class, () -> {
            calc.divide(10, 0);
        });
    }
}

C++ (Google Test):

#include <gtest/gtest.h>
#include "calculator.hpp"

class CalculatorTest : public ::testing::Test {
protected:
    void SetUp() override {
        calc = std::make_unique<Calculator>();
    }

    std::unique_ptr<Calculator> calc;
};

TEST_F(CalculatorTest, ShouldAddNumbers) {
    EXPECT_EQ(5, calc->add(2, 3));
}

TEST_F(CalculatorTest, ShouldThrowOnDivideByZero) {
    EXPECT_THROW(calc->divide(10, 0), std::invalid_argument);
}

Mockito → Google Mock

Java (Mockito):

@Mock
private UserRepository repo;

@Test
void shouldFindUser() {
    when(repo.findById(1L)).thenReturn(Optional.of(user));

    User result = service.getUser(1L);

    verify(repo).findById(1L);
    assertEquals("Alice", result.getName());
}

C++ (Google Mock):

class MockUserRepository : public UserRepository {
public:
    MOCK_METHOD(std::optional<User>, findById, (int64_t id), (override));
};

TEST(UserServiceTest, ShouldFindUser) {
    MockUserRepository repo;
    UserService service(repo);
    User expected{"Alice", 30};

    EXPECT_CALL(repo, findById(1))
        .WillOnce(::testing::Return(expected));

    auto result = service.getUser(1);
    EXPECT_EQ("Alice", result.getName());
}

Common Pitfalls

1. Object Slicing

Problem:

class Base {
public:
    virtual void print() { std::cout << "Base\n"; }
};

class Derived : public Base {
    int extra;
public:
    void print() override { std::cout << "Derived\n"; }
};

void process(Base obj) {  // Takes by value - SLICING!
    obj.print();  // Always prints "Base"
}

Derived d;
process(d);  // Derived object sliced to Base

Fix:

void process(const Base& obj) {  // Take by reference
    obj.print();  // Polymorphic behavior
}

2. Forgetting Virtual Destructors

Problem:

class Base {
public:
    ~Base() { /* cleanup */ }  // Not virtual!
};

class Derived : public Base {
    int* data;
public:
    ~Derived() { delete[] data; }  // Never called!
};

Base* ptr = new Derived();
delete ptr;  // Undefined behavior - Derived destructor not called

Fix:

class Base {
public:
    virtual ~Base() { /* cleanup */ }  // Virtual destructor
};

3. Returning Dangling References

Problem:

const std::string& getName() {
    std::string name = "temp";
    return name;  // Returns reference to destroyed object!
}

Fix:

std::string getName() {
    return "temp";  // Return by value (move semantics)
}

// Or if truly returning member:
const std::string& getName() const {
    return memberName;  // OK - member outlives function
}

4. Iterator Invalidation

Problem:

std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto it = vec.begin(); it != vec.end(); ++it) {
    if (*it == 3) {
        vec.erase(it);  // it is now invalid!
    }
}

Fix:

for (auto it = vec.begin(); it != vec.end();) {
    if (*it == 3) {
        it = vec.erase(it);  // erase returns next valid iterator
    } else {
        ++it;
    }
}

// Or use erase-remove idiom
vec.erase(std::remove(vec.begin(), vec.end(), 3), vec.end());

5. Copying Large Objects Unnecessarily

Problem:

std::vector<int> getData() {
    std::vector<int> data(1000000);
    // fill data
    return data;  // Looks like copy but OK (NRVO)
}

void process(std::vector<int> data) {  // Copy!
    // Use data
}

Fix:

// Return by value is OK (move semantics / NRVO)
std::vector<int> getData() {
    std::vector<int> data(1000000);
    return data;  // Moved or elided
}

// Take by const reference if not modifying
void process(const std::vector<int>& data) {
    // Use data
}

// Take by rvalue reference if consuming
void process(std::vector<int>&& data) {
    myData = std::move(data);
}

6. String Null-Termination Confusion

Problem:

// Java strings are not null-terminated
// C++ std::string is, but C-style strings require care

char buffer[10];
std::string str = "0123456789";  // 10 chars
strcpy(buffer, str.c_str());  // Buffer overflow! Need 11 bytes for \0

Fix:

char buffer[11];  // One extra for null terminator
std::strncpy(buffer, str.c_str(), sizeof(buffer) - 1);
buffer[sizeof(buffer) - 1] = '\0';  // Ensure null termination

// Or better: just use std::string
std::string buffer = str;

7. Unsigned Integer Underflow

Problem:

// Java doesn't have unsigned types (except char)
// C++ does, and they can underflow

size_t count = 0;
count--;  // Underflows to SIZE_MAX (very large number)!

for (size_t i = vec.size() - 1; i >= 0; --i) {  // Infinite loop!
    // Process vec[i]
}

Fix:

// Use signed for arithmetic that can go negative
int count = 0;
count--;  // -1

// Reverse iteration
for (size_t i = vec.size(); i-- > 0;) {
    // Process vec[i]
}

// Or use reverse iterators
for (auto it = vec.rbegin(); it != vec.rend(); ++it) {
    // Process *it
}

8. Const Correctness

Problem:

// Java doesn't enforce const, C++ does

class Data {
    std::string value;
public:
    std::string getValue() {  // Not const!
        return value;
    }
};

void print(const Data& data) {
    std::cout << data.getValue();  // Error: calling non-const method on const object
}

Fix:

class Data {
    std::string value;
public:
    const std::string& getValue() const {  // Const method
        return value;
    }
};

Tooling

| Tool | Purpose | Notes | |------|---------|-------| | CMake | Build system | De facto standard | | vcpkg | Package manager | Microsoft's package manager | | Conan | Package manager | Decentralized, flexible | | Google Test | Testing framework | Most popular | | Catch2 | Testing framework | Header-only, BDD style | | Google Mock | Mocking framework | Works with Google Test | | Clang-Format | Code formatter | Based on LLVM | | Clang-Tidy | Static analyzer | Catches common errors | | Valgrind | Memory debugger | Detects leaks, errors | | AddressSanitizer | Memory error detector | Part of Clang/GCC | | Doxygen | Documentation | Javadoc equivalent |


Examples

Example 1: Simple - User Class

Java:

public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

C++:

#include <string>
#include <sstream>

class User {
private:
    std::string name;
    int age;

public:
    User(std::string name, int age)
        : name(std::move(name)), age(age) {}

    const std::string& getName() const {
        return name;
    }

    int getAge() const {
        return age;
    }

    std::string toString() const {
        std::ostringstream oss;
        oss << "User{name='" << name << "', age=" << age << "}";
        return oss.str();
    }
};

Example 2: Medium - Repository Pattern

Java:

public interface UserRepository {
    Optional<User> findById(Long id);
    List<User> findAll();
    void save(User user);
    void delete(Long id);
}

public class InMemoryUserRepository implements UserRepository {
    private Map<Long, User> users = new HashMap<>();

    @Override
    public Optional<User> findById(Long id) {
        return Optional.ofNullable(users.get(id));
    }

    @Override
    public List<User> findAll() {
        return new ArrayList<>(users.values());
    }

    @Override
    public void save(User user) {
        users.put(user.getId(), user);
    }

    @Override
    public void delete(Long id) {
        users.remove(id);
    }
}

C++:

#include <unordered_map>
#include <vector>
#include <optional>
#include <memory>

class UserRepository {
public:
    virtual ~UserRepository() = default;

    virtual std::optional<User> findById(int64_t id) = 0;
    virtual std::vector<User> findAll() = 0;
    virtual void save(const User& user) = 0;
    virtual void remove(int64_t id) = 0;
};

class InMemoryUserRepository : public UserRepository {
private:
    std::unordered_map<int64_t, User> users;

public:
    std::optional<User> findById(int64_t id) override {
        auto it = users.find(id);
        if (it != users.end()) {
            return it->second;
        }
        return std::nullopt;
    }

    std::vector<User> findAll() override {
        std::vector<User> result;
        result.reserve(users.size());
        for (const auto& [_, user] : users) {
            result.push_back(user);
        }
        return result;
    }

    void save(const User& user) override {
        users[user.getId()] = user;
    }

    void remove(int64_t id) override {
        users.erase(id);
    }
};

Example 3: Complex - Service with Dependencies

Java:

public class UserService {
    private final UserRepository repository;
    private final EmailService emailService;
    private final Logger logger;

    public UserService(UserRepository repository,
                      EmailService emailService) {
        this.repository = repository;
        this.emailService = emailService;
        this.logger = LoggerFactory.getLogger(UserService.class);
    }

    public User createUser(String name, String email, int age) {
        logger.info("Creating user: {}", name);

        if (name == null || name.isEmpty()) {
            throw new IllegalArgumentException("Name cannot be empty");
        }

        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("Invalid age: " + age);
        }

        User user = new User(name, email, age);
        repository.save(user);

        try {
            emailService.sendWelcomeEmail(user);
        } catch (EmailException e) {
            logger.warn("Failed to send welcome email", e);
        }

        return user;
    }

    public List<User> getActiveUsers() {
        return repository.findAll().stream()
            .filter(User::isActive)
            .sorted(Comparator.comparing(User::getName))
            .collect(Collectors.toList());
    }

    public Optional<User> updateUserAge(Long id, int newAge) {
        Optional<User> userOpt = repository.findById(id);

        if (userOpt.isPresent()) {
            User user = userOpt.get();
            user.setAge(newAge);
            repository.save(user);
            logger.info("Updated user {} age to {}", id, newAge);
        }

        return userOpt;
    }
}

C++:

#include <memory>
#include <string>
#include <vector>
#include <optional>
#include <algorithm>
#include <spdlog/spdlog.h>  // Popular logging library

class UserService {
private:
    std::shared_ptr<UserRepository> repository;
    std::shared_ptr<EmailService> emailService;
    std::shared_ptr<spdlog::logger> logger;

public:
    UserService(std::shared_ptr<UserRepository> repository,
                std::shared_ptr<EmailService> emailService)
        : repository(std::move(repository))
        , emailService(std::move(emailService))
        , logger(spdlog::get("UserService")) {
        if (!logger) {
            logger = spdlog::stdout_color_mt("UserService");
        }
    }

    User createUser(const std::string& name, const std::string& email, int age) {
        logger->info("Creating user: {}", name);

        if (name.empty()) {
            throw std::invalid_argument("Name cannot be empty");
        }

        if (age < 0 || age > 150) {
            throw std::invalid_argument("Invalid age: " + std::to_string(age));
        }

        User user(name, email, age);
        repository->save(user);

        try {
            emailService->sendWelcomeEmail(user);
        } catch (const EmailException& e) {
            logger->warn("Failed to send welcome email: {}", e.what());
        }

        return user;
    }

    std::vector<User> getActiveUsers() {
        auto users = repository->findAll();

        // Filter active users
        std::vector<User> active;
        std::copy_if(users.begin(), users.end(), std::back_inserter(active),
            [](const User& u) { return u.isActive(); });

        // Sort by name
        std::sort(active.begin(), active.end(),
            [](const User& a, const User& b) {
                return a.getName() < b.getName();
            });

        return active;
    }

    // Or with C++20 ranges
    std::vector<User> getActiveUsersRanges() {
        auto users = repository->findAll();

        auto active = users
            | std::views::filter([](const User& u) { return u.isActive(); })
            | std::ranges::to<std::vector>();

        std::ranges::sort(active, {}, &User::getName);

        return active;
    }

    std::optional<User> updateUserAge(int64_t id, int newAge) {
        auto userOpt = repository->findById(id);

        if (userOpt.has_value()) {
            User& user = *userOpt;
            user.setAge(newAge);
            repository->save(user);
            logger->info("Updated user {} age to {}", id, newAge);
        }

        return userOpt;
    }
};

See Also

For more examples and patterns, see:

  • meta-convert-dev - Foundational patterns with cross-language examples
  • convert-golang-rust - Similar systems language conversion patterns
  • lang-java-dev - Java development patterns
  • lang-cpp-dev - C++ development patterns

Cross-cutting pattern skills:

  • patterns-concurrency-dev - Threading, mutexes, async patterns
  • patterns-serialization-dev - JSON, data validation across languages
  • patterns-metaprogramming-dev - Templates, generics, annotations