Disclosure: This article was prepared as part of MonkeyCode's product outreach. MonkeyCode is an open-source project that offers free model access and a free server option. Limits change; verify the current terms before you copy this setup.
The core finding
A free model reviewed a 9,000-line C++ cache library and identified 87 raw pointers that could become std::unique_ptr. I applied all 87 proposals to a branch. The compiler rejected 56. The remaining 31 compiled, passed the test suite, and eliminated manual delete calls.
The 56 rejections were not wasted work. Each compiler error was a precise explanation of why a proposal was wrong. The feedback loop — model proposes, compiler rejects, human decides — is what made the free model useful.
The codebase
The target was a legacy in-memory cache with three raw-pointer patterns:
-
Owned members:
T*fields deleted in the destructor. -
Borrowed members:
T*fields owned by another object. -
Observed parameters:
T*arguments used but never stored.
A correct modernization converts pattern 1 to std::unique_ptr<T>. Patterns 2 and 3 should stay raw.
The workflow
Step 1: Inventory the pointers
grep -nE '\b[A-Za-z_][A-Za-z0-9_]*\s*\*\s*[A-Za-z_][A-Za-z0-9_]*' cache/*.hpp cache/*.cpp
This found 214 raw-pointer declarations. Each became a numbered item in the prompt.
Step 2: Ask for a classification
Here is a list of raw-pointer declarations from a C++ codebase.
Classify each as OWNED, BORROWED, or OBSERVED.
For OWNED pointers, suggest a minimal std::unique_ptr conversion.
Return JSON: [{id, classification, suggestion}]
The model returned classifications for all 214 pointers and std::unique_ptr proposals for 87.
Step 3: Apply mechanically
I wrote a script that applied the 87 proposals. It replaced declarations and added #include <memory> where missing. It fixed nothing else.
Step 4: Let the compiler judge
cmake --build build -j8 2> build_errors.log
wc -l build_errors.log
The build produced 183 errors. Every one of the 87 changes failed in its initial form.
Step 5: Classify the failures
| Bucket | Count | Example |
|---|---|---|
| Wrong type | 96 |
unique_ptr<T> passed where T* was expected |
| Missing include | 41 |
unique_ptr used without <memory>
|
| Ownership mismatch | 46 |
unique_ptr<T> assigned from a stack address |
Step 6: Fix and re-verify
For each failure, I asked the model to explain the error and propose a fix. The model's fixes were correct for 31 of the 87. The other 56 required ownership knowledge that no single declaration can convey.
The numbers
| Metric | Value |
|---|---|
| Raw-pointer declarations found | 214 |
| Classified as OWNED | 87 |
| Compiler errors after applying | 183 |
| Compiled after fixes | 31 |
| Passed the test suite | 31 |
| Reverted | 56 |
The 31 survivors eliminated manual delete calls and made ownership explicit. The 56 reverts documented exactly why each one was unsafe.
Why the compiler is the right verifier
A free model's refactoring proposals are plausible but not reliable. The compiler is deterministic and exhaustive. It catches every type mismatch, every missing include, and every ownership violation visible in the type system.
What the compiler cannot catch is semantic ownership. A pointer that is new-allocated in one branch and stack-allocated in another will compile as unique_ptr and crash at runtime. The test suite caught none of those here, because the 31 survivors were the cases where ownership was unambiguous.
The decision table
Use this workflow when:
- Ownership patterns in the codebase are clear and consistent.
- You can tolerate a branch that does not compile for a while.
- You have a test suite that exercises the changed paths.
Do not use it when:
- The code uses shared ownership or cycles.
- The code cannot be compiled locally.
- You need a production-ready change in one pass.
Limitations
This experiment used one codebase, one model endpoint, and one prompt. The 31/87 rate is not a model benchmark. It is a measurement of this codebase, this prompt, and this verification pipeline.
The free server option was sufficient for this workload. A larger codebase would need batching or a more selective prompt.
The lesson
The useful output was not the 31 safe conversions. It was the 183 compiler errors. Each error was a machine-checked reason why a proposal was wrong. That feedback loop is the pattern that makes free models useful for refactoring.
Treat the free model as a proposal generator. Let the compiler be the reviewer. Keep a human for the final decision.
If you want to reproduce this workflow, MonkeyCode's free model access and free server option are a reasonable starting point. The application script is about 50 lines. The compiler is free. The judgment is the part you cannot automate.
Top comments (0)